/**
 * Nettle specific code
 * (C) Nettle developers 2000-2003
 *
 * $Id: nettle,v 1.121 2004/04/03 10:18:51 archifishal Exp $
 */

#include "generic.h"
#include "globals.h"

#include "lineedit.h"
#include "main.h"
#include "messages.h"
#include "misc.h"
#include "mouse.h"
#include "nettle.h"
#include "process.h"
#include "seln.h"
#include "socket.h"
#include "spool.h"
#include "templ.h"
#include "wimp.h"
#include "wimputil.h"
#include "zapredraw.h"

static void start_connection_int (struct connection_params *params );
static bool get_user_pass (char *string, char *tempbuf, struct connection_params *params);
static int get_host_port (char *string, int *port);

static void rehash_blink(void)
{
  struct session_struct *session = sessions;
  want_blink = false;

  for(;session;session=session->next)
  {
    if (session->want_blink)
    {
      want_blink = true;
      return;
    }
  }
}

/*
 * Description:  Read the user and password out of a string, modifying
 *               it to contain just the host/port pair
 * Parameters:   string-> the string to parse, and modify
 *               tempbuf-> the string to use for holding password
 *                         and user name in (guarenteed as long as
 *                         string+2)
 *               params-> parameters block to update, we write into
 *                        login_user and login_pass a pointer into the
 *                        tempbuf for the user and password
 * Returns:      true if we succeeded
 */
static bool get_user_pass (char *string, char *tempbuf, struct connection_params *params)
{
  char *sptr;

  if (! (string && *string) )
  {
    generror("InvalidAddr", true);
    return false;
  }

  /* strip off telnet: if it's there */
  if (strncmp (string, "telnet:", 7) == 0)
    strcpy (string, string + 7);

  /* strip out any /s anywhere in the string */
  {
    char *in = string;
    char *out = string;
    while (*in)
    {
      char c = *in++;
      if (c != '/')
        *out++=c;
    }
    *out='\0';
  }

  sptr = strchr(string, '@');
  if (sptr)
  {
    char *end=&sptr[1];
    char *colon;
    *sptr='\0';
    colon=strchr(string,':');

    if (colon != string)
    {
      /* We have <user>[:<pass>] now */
      if (colon && colon[1]!='\0')
      {
        *colon++ = '\0';
        strcpy(tempbuf,colon);
        params->login_pass = tempbuf;
        tempbuf += strlen(tempbuf)+1;
      }
      params->login_user = tempbuf;
      strcpy(tempbuf,string);
      tempbuf += strlen(tempbuf)+1;
    }
    else
    {
      /* Probably invalid, but we'll just assume they meant nothing */
    }

    /* Finally, copy the hostname to the end */
    strcpy(string,end);
  }
  else
  {
    /* These should already be set to NULL */
#if 0
    params->login_user = NULL;
    params->login_pass = NULL;
#endif
  }

  /* success */
  return true;
}

static int get_host_port (char *string, int *port)
/* modifies string, returns true on success */
{
  char *sptr;

  if (! *string)
  {
    generror("InvalidAddr", true);
    return false;
  }

  /* strip off telnet: if it's there */
  if (strncmp (string, "telnet:", 7) == 0)
    strcpy (string, string + 7);

  /* strip out any /s anywhere in the string */
  {
    char *in = string;
    char *out = string;
    while (*in)
    {
      char c = *in++;
      if (c != '/')
        *out++=c;
    }
    *out='\0';
  }

  /* look at the string to find a port if any (separated by colon, space or comma) */
  sptr = strpbrk (string, ": ,");
  if (sptr)
  {
    *sptr = '\0';
    *port = atoi (sptr + 1);
  }
  else
    *port = 23;

  /* if the port is <1, then it's either invalid or was too big and got wrapped */
  if (*port < 1)
  {
    generror("InvalidPort", true);
    return false;
  }

  /* success */
  return true;
}

bool start_connection_friedport(struct connection_params *params )
{
  int port;
  char host[1024];
  char tempbuf[1024];

  if (params->connection_type != NETTLE_TASKWINDOW)
  {
    if (!params->host)
      return false;

    strcpy(host,params->host);

    if (get_user_pass(host, tempbuf, params) == false)
      return false;

    if (get_host_port (host, &port) == false)
      return false;

    params->host = host;
    params->port = port;
  }

  start_connection_int(params);

  return true;
}


bool nettle_start_telnet_connection(const char *host, int port)
{
  struct connection_params params;

  params.connection_type  = NETTLE_TELNET;
  params.terminal_type    = default_terminal;
  params.host             = host;
  params.port             = port;
  params.command          = NULL;
  params.width            = defaultsize.x;
  params.height           = defaultsize.y;
  params.scrollback       = defaultscroll;
  params.line_editor_type = line_editor;
  sprintf(params.label, "%.*s:%d", (int)sizeof(params.label) - 16, host, port);

  start_connection_int(&params);

  return true;
}



bool nettle_start_telnet_connection_friedport(const char *host)
{
  struct connection_params params;

  params.connection_type  = NETTLE_TELNET;
  params.terminal_type    = default_terminal;
  params.host             = host;
  params.port             = -1;
  params.command          = NULL;
  params.width            = defaultsize.x;
  params.height           = defaultsize.y;
  params.scrollback       = defaultscroll;
  params.line_editor_type = line_editor;
  sprintf(params.label, "%.*s", (int)sizeof(params.label) - 16, host );

  start_connection_friedport(&params);

  return true;
}



bool nettle_start_taskwindow(const char *cmd)
{
  struct connection_params params;

  params.connection_type  = NETTLE_TASKWINDOW;
  params.terminal_type    = default_terminal;
  params.host             = NULL;
  params.port             = 0;
  params.command          = cmd;
  params.width            = defaultsize.x;
  params.height           = defaultsize.y;
  params.scrollback       = defaultscroll;
  params.line_editor_type = line_editor;
  params.label[0]         = '\0';
  params.login_user       = NULL;
  params.login_pass       = NULL;
  strncat(params.label, cmd, sizeof(params.label) - 1);

  start_connection_int(&params);

  return true;
}


bool start_connection(void)
{
  struct connection_params params;
  char string[1024];
  char tempbuf[1024];
  int loop;
  int connection_type;
  enum nettle_terminal terminal_type;
  int port;

  read_icon_data(win_open, icon_open_contype, string, sizeof(string));

  {
    char connection[MESSAGE_MAX_CONNECTION];

    if (strcmp(string, lookup("SSH", connection, MESSAGE_MAX_CONNECTION))==0)
      connection_type=NETTLE_SSH;
    else if (strcmp(string, lookup("Taskwindow", connection, MESSAGE_MAX_CONNECTION))==0)
      connection_type=NETTLE_TASKWINDOW;
    else
      connection_type=NETTLE_TELNET;
  }

  terminal_type = read_terminal_type (win_open, icon_open_termtype);

  params.connection_type  = connection_type;
  params.terminal_type    = terminal_type;
  params.host             = NULL;
  params.port             = 0;
  params.command          = NULL;
  params.width            = defaultsize.x;
  params.height           = defaultsize.y;
  params.scrollback       = defaultscroll;
  params.line_editor_type = line_editor;
  params.label[0]         = '\0';
  params.login_user       = NULL;
  params.login_pass       = NULL;

  switch (connection_type)
  {
    case NETTLE_TELNET:
    case NETTLE_SSH:
      read_icon_data(win_open, icon_open_host, string, sizeof(string));

      if (get_user_pass(string, tempbuf, &params) == false)
        return false;

      if (get_host_port(string, &port) == false)
        return false;

      params.host = string;
      params.port = port;
      start_connection_int (&params);
      break;

    case NETTLE_TASKWINDOW:
      read_icon_data (win_open, icon_open_command, string, sizeof (string));
      loop = strlen (string);
      while (string[loop - 1] == ' ')
        loop--;
      string[loop] = '\0';		/* trim trailing spaces */
      loop = strspn (string, " ");	/* number of leading spaces */

      params.command = string+loop;
      start_connection_int (&params);
      break;


    default:
      break;
  }
  return true;
}

/*
 * Description:  Create a new window for the terminal, based on the
 *               information in the session block, and write into the
 *               session block the details
 * Parameters:   session-> the session to create the window for and
 *               to update
 * Returns:      none
 */
static void make_new_terminal_window(struct session_struct *session)
{
  static int max_y = -1; /* the top of the next window to be opened, or -1
                            to generate a new position */
  struct wimp_createwindow_block block;

  if (max_y == -1)
  {
    /* First window, or a reset window */

    /* We want to position the window toward the top of the screen, so the
     * algorithm we use is simple - 7/8 of the way up the screen, which is
     * reasonable for nearly all screen sizes
     */
    max_y = (screensize.y*7)/8;
  }

  block.min.x         =102;
  block.min.y         =max_y-(session->terminal_size.y*redraw.r_charh << eig.y);
  block.max.x         =102+(session->terminal_size.x*redraw.r_charw << eig.x);
  block.max.y         =max_y;
  block.scroll.x      =0;
  block.scroll.y      =-((session->terminal_size.y+
                         session->scrollback)
                        *(redraw.r_charh << eig.y));
  block.handle_behind =-1;
  block.window_flags  =0xff00c002;
  block.title_fg      =7;
  block.title_bg      =2;
  block.work_fg       =7;
  block.work_bg       =0xFF;

  block.scroll_outer_colour=3;
  block.scroll_inner_colour=1;
  block.title_focus_colour =12;
  block.flags              =0;

  block.work_min.x=0;
  block.work_min.y=-((session->terminal_size.y+
                     session->scrollback)
                    *(redraw.r_charh << eig.y));
  block.work_max.x=session->terminal_size.x*redraw.r_charw << eig.x;
  block.work_max.y=0;

  block.icon_flags    =0x00000119;
  block.workarea_flags=10<<12; /* clicks (*256), drags (*16), double clicks (*1) */
  block.sprite_area   =(struct os_spriteop_area *) 1;
  block.min_width     =4;
  block.min_height    =4;

  block.title.it.text      =session->title;
  block.title.it.validation=0;
  block.title.it.text_len  =256;

  block.number_of_icons    =0;

  session->window_handle = _swi(Wimp_CreateWindow, _IN(1)|_RETURN(0), &block);

  set_title_bar(session->window_handle,"Nettle");

  open_window(NULL, session->window_handle);

  {
    /* update the next window position */
    struct wimp_getwindowstate_block window;
    window.window_handle = session->window_handle;
    _swi (Wimp_GetWindowState, _IN(1), (int) &window);

    max_y = (window.min.y < 256) ? -1 : window.max.y - 40;
  }
}

/* **** Login details - icky! **** */

/* What we do is we match each character against the data we expect to
   get; if a character matches, we increment the index into the string.
   If the next character in the match string is a \n, we know we've
   completed a match (because this is a MessageTrans buffer string)
   and we act upon it. If it does NOT match, we reset the index to 0.
 */


/*
 * Description:  Data has been received from the remote end of the
 *               connection; we need to either match the current
 *               string, or reset our counter
 * Parameters:   session-> the session we're part of
 *               c = character received
 * Returns:      none
 */
static void login_data(struct session_struct *session, char c)
{
  if (c == session->login_match[ session->login_index ])
  {
    session->login_index++;
    if (session->login_match[ session->login_index ] == '\n')
    {
      /* We've reached the end of a match string, so we need to act upon it */

      if (session->login_user != NULL)
      {
        /* We matched the username request, so send the string they
           provided us with */

        nettle_senddata(session,session->login_user, strlen(session->login_user) );
        nettle_senddata(session,"\r", 1);

        free(session->login_user);
        session->login_user = NULL;
      }
      else if (session->login_pass != NULL)
      {
        /* We matched the password request, so send the string they
           provided us with */

        nettle_senddata(session,session->login_pass, strlen(session->login_pass) );
        nettle_senddata(session,"\r", 1);

        free(session->login_pass);
        session->login_pass = NULL;
      }

      /* Having acted upon the matched string, we now move on to the
         next action */
      if (session->login_pass != NULL)
      {
        session->login_match = lookup_inbuffer("LoginMatch_Pass");
        assert(session->login_match);
      }
      else
      {
        /* All done. Remove our processing routines */
        session->login_data = NULL;
      }

      /* Remember to reset the index; doesn't hurt if we're already
         complete */
      session->login_index = 0;
    }
  }
  else
  {
    /* reset the character counter */
    session->login_index = 0;
  }
}



static void start_connection_int (struct connection_params *params )
{
  int loop;
  struct session_struct *session;

  if (params->label[0]=='\0')
  {
    switch(params->connection_type)
    {
    case NETTLE_TASKWINDOW:
      if (params->command)
        strcpy(params->label, params->command);
      else
        sprintf(params->label, "<%s>", lookup_static("taskwindow"));
      break;

    case NETTLE_TELNET:
    case NETTLE_SSH:
      {
        /* Provide a label in the form [<user>@]<host>[:<port>] */
        char *tail=params->label;

        if (!params->host)
          return;
        if (params->login_user)
          tail+=sprintf(tail,"%s@",params->login_user);
        if ( (params->connection_type == NETTLE_TELNET && params->port!=23) ||
             (params->connection_type == NETTLE_SSH    && params->port!=22) )
          sprintf(tail, "%s:%d", params->host, params->port);
        else
          strcpy(tail, params->host);
      }
      break;
    }
  }

  session = malloc(sizeof(struct session_struct));
  if (session == NULL)
  {
    generror("OutOfMem", true);
    return;
  }

  /* Insert into list */
  session->prev = NULL;
  session->next = sessions;
  if (sessions)
  {
    sessions->prev = session;
  }
  sessions = session;

  /* clear all the session flags */
  for (loop=0; loop<256; loop++)
  {
    session->session_flags[loop]=false;
  }
  session->local_echo = true;
  session->want_blink = false;
  session->socket_state = NETTLE_SESSION_NONE;

  session->dns = NULL; /* Should be null for non telnet/ssh sessions */

  switch (params->connection_type)
  {
    case NETTLE_TELNET:
    case NETTLE_SSH:
      /* host, port already parsed */
      session->socket_handle=-1;
      session->socket_state=NETTLE_SESSION_RESOLVE;
      session->socket_port = params->port;
      strcpy (session->socket_host, params->host);
      session->dns = dns_gethostbyname(session->socket_host);
      assert(session->dns != NULL);

      if (params->connection_type==NETTLE_SSH)
      {
        session->local_echo=false;
      }

      break;

    case NETTLE_TASKWINDOW:
      /* start the task window */
      {
        char temp_string[1024];

        sprintf(temp_string, "%d", params->height);
        _swi(OS_SetVarVal, _INR(0,4), "LINES", temp_string, strlen(temp_string), 0, 4);
        _swi(OS_SetVarVal, _INR(0,4), "ROWS", temp_string, strlen(temp_string), 0, 4); /* for old unixlib progs */
        sprintf(temp_string, "%d", params->width);
        _swi(OS_SetVarVal, _INR(0,4), "COLUMNS", temp_string, strlen(temp_string), 0, 4);

        if (params->command && params->command[0]!='\0')
        {
           FILE *pipefile;

          /* To disable escape in ANSI TaskWindows, we need to execute
             *FX 229,1 before the task proper is run.  Make an Obey file
             in Pipe: to achieve this */

           /* Race condition here with such a static filename */
           pipefile = fopen("Pipe:$.NettleTask", "w");
           if (!pipefile)
           {
             generror("NoStartTask", true);
             return;
           }
           else
           {
             fprintf(pipefile, "fx 229 1\n%s\n", params->command);
             fclose(pipefile);
             misc_setfiletype("Pipe:$.NettleTask", 0xfeb);

             {
               char *temp_command=malloc((strlen(params->command)+1)*2); /* At most, everything will be a " */
               int i=0;

               assert(temp_command);

               strcpy(temp_command, "");

               while (i<strlen(params->command))
               {
                 switch (params->command[i])
                 {
                   case '"':
                     strcat(temp_command,"\"\"");
                     break;
                   default:
                     temp_command[strlen(temp_command)+1]='\0';
                     temp_command[strlen(temp_command)]=params->command[i];
                     break;
                 }

                 i++;
               }
               sprintf (temp_string, "%%TaskWindow \"%%Obey -c Pipe:$.NettleTask\" -ctrl -name \"Nettle task - %s\"" \
                          " -quit -task &%X -txt &%X", temp_command, task_handle, (int)session);

               free(temp_command);
             }
           }
        }
        else
        {
          sprintf (temp_string, "%%TaskWindow -ctrl -name \"Nettle task\"" \
                     " -task &%X -txt &%X", task_handle, (int)session);
        }



        _swi(Wimp_StartTask, _IN(0), temp_string);
      }

      /* set ECHO bit to true */
      session->session_flags[1]=true;
      session->local_echo = false;

      break;
  }

  /* set all the attributes to their defaults */
  strcpy(session->label, params->label);
  session->terminal_size.x = params->width;
  session->terminal_size.y = params->height;
  session->scrollback = params->scrollback;
  session->title=malloc(256);
  assert(session->title != NULL);
  strcpy(session->title,"");

  session->login_user = params->login_user ? strdup(params->login_user) : NULL;
  session->login_pass = params->login_pass ? strdup(params->login_pass) : NULL;

  if (params->login_user)
    assert(session->login_user);
  if (params->login_pass)
    assert(session->login_pass);

  if (session->login_user && params->connection_type!=NETTLE_TASKWINDOW)
  {
    /* Where a user/password exists, we want to set up the login details */
    session->login_index = 0; /* We're 0 characters into the match string */
    session->login_match = lookup_inbuffer("LoginMatch_User");
    assert(session->login_match);

    /* Function pointers for handling matching */
    session->login_data = login_data;
  }
  else
  {
    /* If we have no user, we have no data function */
    session->login_data = NULL;
  }

  session->icon_name=malloc(256);
  assert(session->icon_name != NULL);
  strcpy(session->icon_name,"");

  create_terminal(session, session->terminal_size, session->scrollback);

  session->pos.x=0;
  session->pos.y=session->scrollback;
  session->connection_type=params->connection_type;
  session->terminal_type=params->terminal_type;
  session->paste=NULL;
  session->paste_head=NULL;
  session->window_needs_resized=false;
  reset_terminal(session);

  /* clear out the assigned area with black bg, white fg, no flags and ' ' */
  clear_screen(session,
               0,
               session->terminal_size.x*(session->terminal_size.y+session->scrollback),
               NETTLE_COLOUR_NORMAL_WHITE,
               NETTLE_COLOUR_NORMAL_BLACK,
               0,
               ' ');

  copy_main_alternate(session);

  /* create tabs */
  session->number_of_tabs=0;
  for (loop=0; loop<session->terminal_size.x; loop+=8)
  {
    session->tabs[session->number_of_tabs]=loop;
    session->number_of_tabs++;
  }

  session->line_editor_type=params->line_editor_type;

  session->spool_file_name=NULL; /* make sure filename is undefined */

  /* if line editor is turned on, allocate all the lineeditor space */
  if (session->line_editor_type==LINEEDIT_NONE)
  {
    session->pane_handle=0;
    session->line_editor_history=NULL;
  }
  else
    lineedit_allocate_space(session, false);

  make_new_terminal_window(session);

  switch (session->line_editor_type)
  {
    case LINEEDIT_CHECKBOX_ON:
    case LINEEDIT_ANTTERM:
      set_caret_position(session->pane_handle,0,-1,get_icon_data_length(session->pane_handle, 0));
      break;
    default:
      set_caret_position(session->window_handle,-1,-1,-1);
      break;
  }

  switch (params->connection_type)
  {
    case NETTLE_TELNET:
    case NETTLE_SSH:
      set_title_bar(session->window_handle, lookup_static ("title_conn"));
      write_out_strings (session,
                         lookup_static_var ("lookup", 1,
                                            session->socket_host),
                          "\r\n", 0);
      main_requirenull = true;
      break;
  }

  allocate_zapredraw_area(session->terminal_size.x, session->terminal_size.y, session->scrollback);

  {
    char cursor_flags;
    struct coords pos;

    pos=get_cursor_position(session);

    /* get the cursor flags */
    cursor_flags=read_assigned_flags(session, pos.x, pos.y) &
          (NETTLE_FLAG_CURSOR | NETTLE_FLAG_NO_INPUT);

    /* if the cursor flags don't have cursor set, make it so */
    cursor_flags |= NETTLE_FLAG_CURSOR;

    if (session->other_session_flags & NETTLE_OTHER_CURSOR_VIS)
    {
      write_assigned_flags(session, pos.x, pos.y,
                           read_assigned_flags(session, pos.x, pos.y) | cursor_flags);
    }
  }
}

void write_out_string(struct session_struct *session, const char *string)
{
  write_out_data(session, string, strlen(string));
}

void write_out_strings (struct session_struct *session, ...)
{
  va_list va;
  const char *str;
  va_start (va, session);

  while ((str = va_arg (va, const char *)) != 0)
  {
    write_out_data(session, str, strlen (str));
  }
}

void write_out_data(struct session_struct *session, const char *data, int length_data)
{
  int loop;
  struct coords pos;
  char cursor_flags;

  if (length_data == 0) {
    return;
  }

  pos=get_cursor_position(session);

  /* get the cursor flags */
  cursor_flags=read_assigned_flags(session, pos.x, pos.y) &
        (NETTLE_FLAG_CURSOR | NETTLE_FLAG_NO_INPUT);

  /* if the cursor flags don't have cursor set, make it so */
  cursor_flags |= NETTLE_FLAG_CURSOR;

  /* remove the cursor from the position */
  changedbox_init (session);
  changedbox_update_char (pos.x, pos.y);
  write_assigned_flags(session, pos.x, pos.y,
                       read_assigned_flags(session, pos.x, pos.y) &
                            ~(NETTLE_FLAG_CURSOR | NETTLE_FLAG_NO_INPUT));
  force_redraw_changedbox ();

  changedbox_init (session);

  for (loop = 0; loop < length_data; loop++)
  {
    /* log the byte */
    log_byte(data[loop]);

    /*close_log(true);*/


    /* process the current byte at this position */
    process_data(session, data[loop]);

  }

  pos=get_cursor_position(session);

  /* OR back in the cursor flags */
  if (session->other_session_flags & NETTLE_OTHER_CURSOR_VIS)
  {
    write_assigned_flags(session, pos.x, pos.y,
                         read_assigned_flags(session, pos.x, pos.y) | cursor_flags);
  }

  /* redraw the window */
  force_redraw_changedbox();

  /* force the selection back on (it may have got overwritten by new data) */
  force_redraw_selection();

  /* redraw the cursor, in case it isn't in the main redraw block */
  if (session->other_session_flags & NETTLE_OTHER_CURSOR_VIS)
  {
    changedbox_init (session);
    changedbox_update_char (pos.x, pos.y);
    force_redraw_changedbox ();
  }

  nextcursortime = _swi (OS_ReadMonotonicTime, 0) + CURSOR_BLINK_DELAY;
  cursor_state = true;
}


void remove_session(struct session_struct *session)
{
  /* We unlink *first* such that if there's an error with
   * anything else, the session is effectively dead and we never
   * deal with it ever again. This may result in leaking sockets,
   * and leaking memory if anything fails, but ensures that we
   * don't get repeated errors that take down the application
   * leaking /every/ socket that was in use
   */
  if (session->prev)
  {
    session->prev->next = session->next;
  }
  else
  {
    sessions = session->next;
  }

  if (session->next)
  {
    session->next->prev = session->prev;
  }

  if (session->socket_handle != -1)
  {
    switch (session->connection_type)
    {
      case NETTLE_TELNET:
        socket_close(session->socket_handle);
        break;
      case NETTLE_SSH:
        break;
      case NETTLE_TASKWINDOW:
        {
          /* terminate the taskwindow */
          union wimp_poll_block block;

          block.user_message.length      =20;
          block.user_message.my_ref      =0;
          block.user_message.message_code=WIMP_MESSAGE_TASKWINDOW_MORITE;

          _swi(Wimp_SendMessage, _INR(0,2), 17, &block, session->socket_handle);
        }
        break;
    }
  }
  if (session->dns != NULL)
  {
    dns_dispose(session->dns);
    session->dns = NULL;
  }

  {
    /* delete the window */
    struct wimp_deletewindow_block block;

    block.window_handle=session->window_handle;

    _swi(Wimp_DeleteWindow, _IN(1), &block);

  }

  /* free any paste buffers */
  {
    struct paste_buffer *paste = session->paste;
    while (paste)
    {
      struct paste_buffer *next_paste = paste->next;
      free (paste);
      paste = next_paste;
    }
  }

  /* free lineeditor space */
  lineedit_free_space(session, false);

  /* free auto-login details */
  free(session->login_user);
  free(session->login_pass);

  /* free the icon name */
  free(session->icon_name);

  /* free the data area and title */
  delete_terminal(session);
  free(session->title);

  /* close the spool if any */
  if (session->spool_file_name!=NULL)
  {
    spool_close(session, true);
  }

  /* cancel the selection if there was one in this session */
  if (selection_session == session)
  {
    selection_session = NULL;
  }

  if (cursor_session == session)
    cursor_session = NULL;

  /* close the terminal options window if there was one for this session */
  if (options_session == session)
  {
    close_window(win_termterm);
    options_session = NULL;
  }

  free(session);

  rehash_blink();
}

int nettle_senddata(struct session_struct *session, const char *data, int length)
{
  switch (session->connection_type)
  {
    case NETTLE_TELNET:
      /* just use socket_senddata */
      return socket_senddata(session->socket_handle,data,length);
      break;
    case NETTLE_SSH:
      break;
    case NETTLE_TASKWINDOW:
      {
        /* send a WIMP message */
        union wimp_poll_block block;
        int copy = (length > 219) ? 219 : length;

        memcpy (block.user_message.contents.task_window_input.data, data, copy);

        block.user_message.length                         =(24+3+copy) & ~3;
        block.user_message.my_ref                         =0;
        block.user_message.message_code                   =WIMP_MESSAGE_TASKWINDOW_INPUT;
        block.user_message.contents.task_window_input.size=copy;

        _swi(Wimp_SendMessage, _INR(0,2), 17, &block, session->socket_handle);

        return copy;
      }
      break;
  }
  return 0;
}

void reset_terminal(struct session_struct *session)
{
  session->other_session_flags=NETTLE_OTHER_CURSOR_VIS | NETTLE_OTHER_WRAP_MODE;
  session->current_fg=7;
  session->current_bg=0;
  session->current_flags=0;
  session->escape_string_length=0;
  session->escape_state=NETTLE_ESCAPE_NONE;
  session->scroll_start=1;
  session->scroll_end=session->terminal_size.y;
  session->character_set_gl=NETTLE_CHSET_G0;
  session->character_set_gr=NETTLE_CHSET_G3;

  session->character_set_g0=NETTLE_CHSET_ASCII;
  session->character_set_g1=NETTLE_CHSET_DEC_SUPPLEMENTAL_GRAPHICS;
  session->character_set_g2=NETTLE_CHSET_ASCII;
  session->character_set_g3=NETTLE_CHSET_DEC_SUPPLEMENTAL_GRAPHICS;

  session->cursor_saved=false;

  switch (session->terminal_type)
  {
    case NETTLE_TERMINAL_VT100: case NETTLE_TERMINAL_VT102:
      session->terminal_mode=1;
      break;
    default:
      session->terminal_mode=2;
      break;
  }

  session->bit_controls=false;
  session->want_blink = false;
  rehash_blink();

  session->mouse_terminal_attached=true;
  session->mouse_mode=MOUSE_MODE_OFF;
}

bool valid_x(struct session_struct *session, int xpos)
{
  if (xpos<0 || xpos>=session->terminal_size.x)
  {
    printf("Attempt to read/write outside alternate line, X position %d\n", xpos);
    return false;
  }
  return true;
}

bool valid_y(struct session_struct *session, int ypos)
{
  if (ypos<0 || ypos>=session->terminal_size.y+session->scrollback)
  {
    printf("Attempt to read/write outside assigned area %p, Y position %d\n", session, ypos);
    return false;
  }
  return true;
}

bool valid_alt_y(struct session_struct *session, int ypos)
{
  if (ypos<0 || ypos>=session->terminal_size.y)
  {
    printf("Attempt to read/write outside alternate area %p, Y position %d\n", session, ypos);
    return false;
  }
  return true;
}

void write_assigned(struct session_struct *session, int xpos, int ypos,
                    char fg, char bg, char flags, char character)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    session->assigned_area[ypos][xpos].fg = fg;
    session->assigned_area[ypos][xpos].bg = bg;
    session->assigned_area[ypos][xpos].flags = flags;
    session->assigned_area[ypos][xpos].chr = character;
  }
}

void write_alt_assigned(struct session_struct *session, int xpos, int ypos,
                        char fg, char bg, char flags, char character)
{
  if (valid_x(session, xpos) && valid_alt_y(session, ypos))
  {
    session->alternate_area[ypos][xpos].fg = fg;
    session->alternate_area[ypos][xpos].bg = bg;
    session->alternate_area[ypos][xpos].flags = flags;
    session->alternate_area[ypos][xpos].chr = character;
  }
}

char read_assigned_fg(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    return session->assigned_area[ypos][xpos].fg;
  }

  return 0;
}

char read_assigned_bg(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    return session->assigned_area[ypos][xpos].bg;
  }

  return 0;
}

char read_assigned_flags(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    return session->assigned_area[ypos][xpos].flags;
  }

  return 0;
}

char read_assigned_character(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    return session->assigned_area[ypos][xpos].chr;
  }

  return 0;
}

char read_alt_assigned_fg(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_alt_y(session, ypos))
  {
    return session->alternate_area[ypos][xpos].fg;
  }

  return 0;
}

char read_alt_assigned_bg(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_alt_y(session, ypos))
  {
    return session->alternate_area[ypos][xpos].bg;
  }

  return 0;
}

char read_alt_assigned_flags(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_alt_y(session, ypos))
  {
    return session->alternate_area[ypos][xpos].flags;
  }

  return 0;
}

char read_alt_assigned_character(struct session_struct *session, int xpos, int ypos)
{
  if (valid_x(session, xpos) && valid_alt_y(session, ypos))
  {
    return session->alternate_area[ypos][xpos].chr;
  }

  return 0;
}

void write_assigned_flags(struct session_struct *session, int xpos, int ypos, char flags)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    session->assigned_area[ypos][xpos].flags = flags;
  }
}

void write_assigned_character(struct session_struct *session, int xpos, int ypos, char character)
{
  if (valid_x(session, xpos) && valid_y(session, ypos))
  {
    session->assigned_area[ypos][xpos].chr = character;
  }
}

void create_terminal(struct session_struct *session, struct coords terminal_size,
                     int scrollback)
{
  int loop;

  /****
   **** TO BE DONE - return a failure code from here rather than all the assert()s!
   ****/

  session->assigned_area=malloc((terminal_size.y+scrollback)*sizeof (int *));
  assert(session->assigned_area);

  session->line_flags=malloc( terminal_size.y+scrollback );
  assert(session->line_flags);
  memset( session->line_flags, 0, terminal_size.y+scrollback );

  for (loop=0; loop<terminal_size.y+scrollback; loop++)
  {
    session->assigned_area[loop]=malloc(terminal_size.x*sizeof (struct term_char));
    assert(session->assigned_area[loop]);
  }

  session->alternate_area=malloc(terminal_size.y*sizeof (int *));
  assert( session->alternate_area );

  session->alternate_line_flags=malloc( terminal_size.y );
  assert( session->alternate_line_flags );
  memset( session->alternate_line_flags, 0, terminal_size.y );

  for (loop=0; loop<terminal_size.y; loop++)
  {
    session->alternate_area[loop]=malloc(terminal_size.x*sizeof (struct term_char));
    assert(session->alternate_area[loop]);
  }

  session->terminal_size=terminal_size;
  session->scrollback=scrollback;
}

void delete_terminal(struct session_struct *session)
{
  int loop;

  for (loop=0; loop<session->terminal_size.y+session->scrollback; loop++)
  {
    free(session->assigned_area[loop]);
  }

  free(session->assigned_area);
  free(session->line_flags);

  for (loop=0; loop<session->terminal_size.y; loop++)
  {
    free(session->alternate_area[loop]);
  }

  free(session->alternate_area);
  free(session->alternate_line_flags);
}



static void resize_terminal_dimensions(struct session_struct *session,
                                struct coords terminal_size, int scrollback)
{
  struct coords old_terminal_size = session->terminal_size;
  int total_old_y = old_terminal_size.y + session->scrollback;
  int total_new_y = terminal_size.y + scrollback;
  bool bigger_y = (bool)(total_new_y > total_old_y);
  int area;
  struct term_char clear_char =
   { NETTLE_COLOUR_NORMAL_WHITE, NETTLE_COLOUR_NORMAL_BLACK, 0, ' '};

  session->terminal_size = terminal_size;
  session->scrollback = scrollback;

  /****
   **** TO BE DONE : Attempt to be less crap about line flags here. It'll be very hard!
   ****/

  free( session->line_flags );
  free( session->alternate_line_flags );

  session->line_flags = malloc( terminal_size.y + scrollback );
  memset( session->line_flags, 0, terminal_size.y + scrollback );

  session->alternate_line_flags = malloc( terminal_size.y );
  memset( session->alternate_line_flags, 0, terminal_size.y );

  for (area = 0; area < ((session->alternate_area != NULL) ? 2 : 1); area++)
  {
    struct term_char **new_area;
    struct term_char **old_area;
    int split;
    int loop;

    if (area == 0)
    {
      old_area = session->assigned_area;
      new_area = malloc((terminal_size.y + scrollback) * sizeof(int *));

      if (!new_area)
      {
        generror(lookup_inbuffer("OutOfMem"), false);
        return;
      }

      session->assigned_area = new_area;

      split = total_new_y - total_old_y;
    }
    else
    {
      old_area = session->alternate_area;
      new_area = malloc((terminal_size.y) * sizeof(int *));

      if (!new_area)
      {
        generror(lookup_inbuffer("OutOfMem"), false);
        return;
      }

      session->alternate_area = new_area;

      split = terminal_size.y - old_terminal_size.y;
    }

    /* Copy (total_old_y) lines */

    if (area == 1)
    {
      total_new_y = terminal_size.y;
      total_old_y = old_terminal_size.y;
    }

    if (bigger_y)
    {
      for (loop = total_new_y - 1; loop >= 0; loop--)
      {
        int loop2;
        int old_loop;

        if (area == 0)
        {
          old_loop = loop - (total_new_y - total_old_y);
        }
        else
        {
          old_loop = loop - (terminal_size.y - old_terminal_size.y);
        }

        if (loop >= split)
        {
          new_area[loop] = realloc(old_area[old_loop],
            terminal_size.x * sizeof(struct term_char));

          if (!new_area[loop])
          {
            generror("Nettle has ran out of memory in a rather nasty spot and will die hideously now. Sometime this will be fixed better.", false);
            assert(false);
          }

          /* Clear the rest of the line */
          for (loop2 = old_terminal_size.x; loop2 < terminal_size.x; loop2++)
          {
            new_area[loop][loop2] = clear_char;
          }
        }
        else
        {
          new_area[loop] = malloc(terminal_size.x * sizeof(struct term_char));

          if (!new_area[loop])
          {
            generror("Nettle has ran out of memory in a rather nasty spot and will die hideously now. Sometime this will be fixed better.", false);
            assert(false);
          }

          if (area == 0)
          {
            clear_screen(session, loop * terminal_size.x,
                         (loop + 1) * terminal_size.x,
                         NETTLE_COLOUR_NORMAL_WHITE,
                         NETTLE_COLOUR_NORMAL_BLACK,
                         0,
                         ' ');
          }
          else
          {
           clear_alt_screen(session, loop * terminal_size.x,
                           (loop + 1) * terminal_size.x,
                           NETTLE_COLOUR_NORMAL_WHITE,
                           NETTLE_COLOUR_NORMAL_BLACK,
                           0,
                           ' ');
          }
        }
      }
    }
    else
    {
      for (loop = 0; loop < total_old_y; loop++)
      {
        int loop2;
        int old_loop;

        if (area == 0)
        {
          old_loop = loop - (total_new_y - total_old_y);
        }
        else
        {
          old_loop = loop - (terminal_size.y - old_terminal_size.y);
        }

        if (loop < total_new_y)
        {
          new_area[loop] = realloc(old_area[old_loop],
            terminal_size.x * sizeof(struct term_char));

          if (!new_area[loop])
          {
            generror("Nettle has run out of memory in a rather nasty spot and will die hideously now. Sometime this will be fixed better.", false);
            assert(false);
          }

          /* Clear the rest of the line */
          for (loop2 = old_terminal_size.x; loop2 < terminal_size.x; loop2++)
          {
            new_area[loop][loop2] = clear_char;
          }
        }
        else
        {
          free(old_area[old_loop - total_old_y]);
        }
      }
    }
    free(old_area);
  }
}


void resize_terminal(struct session_struct *session, int terminal_size_x, int terminal_size_y,
                     int scrollback, bool use_naws)
{
  struct coords old_terminal_size;
  int old_scrollback;

  if (terminal_size_x<12) terminal_size_x=12;
  if (terminal_size_y<3) terminal_size_y=3;
  if (scrollback<0) scrollback=0;

  old_terminal_size.x = session->terminal_size.x;
  old_terminal_size.y = session->terminal_size.y;
  old_scrollback      = session->scrollback;

  if (terminal_size_x != old_terminal_size.x || terminal_size_y !=
      old_terminal_size.y || old_scrollback != scrollback)
  {
    struct coords terminal_size;
    char temp_string[12];

    terminal_size.x=terminal_size_x;
    terminal_size.y=terminal_size_y;

    if (session->connection_type == NETTLE_TASKWINDOW)
    {
      sprintf(temp_string, "%d", terminal_size_y);
      _swi(OS_SetVarVal, _INR(0,4), "LINES", temp_string, strlen(temp_string), 0, 4);
      sprintf(temp_string, "%d", terminal_size_x);
      _swi(OS_SetVarVal, _INR(0,4), "COLUMNS", temp_string, strlen(temp_string), 0, 4);
    }

    resize_terminal_dimensions(session, terminal_size, scrollback);

    session->scroll_start   = 1;
    session->scroll_end     = terminal_size_y;

    allocate_zapredraw_area(terminal_size_x, terminal_size_y, scrollback);
  }

  /* This should put the cursor back in the right place... */
  session->pos.y=session->pos.y-old_scrollback-old_terminal_size.y+scrollback+terminal_size_y;
  session->old_pos.y=session->old_pos.y-old_scrollback-old_terminal_size.y+scrollback+terminal_size_y;

  if (session==selection_session)
  {
    /* selection area needs moved to accommodate new terminal size */
    int selection_start_x=selection_start % old_terminal_size.x;
    int selection_start_y=selection_start / old_terminal_size.x;
    int selection_end_x=selection_end % old_terminal_size.x;
    int selection_end_y=selection_end / old_terminal_size.x;

    selection_start_y=selection_start_y-old_scrollback-old_terminal_size.y+scrollback+terminal_size_y;
    selection_end_y=selection_end_y-old_scrollback-old_terminal_size.y+scrollback+terminal_size_y;

    /* do range checking on terminal_size_x */
    if (selection_start_x>terminal_size_x) selection_start_x=terminal_size_x;
    if (selection_end_x>terminal_size_x) selection_end_x=terminal_size_x;

    /* write the selection positions back to the variables */
    selection_start=(selection_start_y*terminal_size_x)+selection_start_x;
    selection_end=(selection_end_y*terminal_size_x)+selection_end_x;

    /* do range checking on the final values of selection_start and selection_end */

    if (selection_start<0) selection_start=0;
    if (selection_end<0) selection_end=0;

    if (selection_start>terminal_size_x*(terminal_size_y+scrollback))
      selection_start=terminal_size_x*(terminal_size_y+scrollback);
    if (selection_end>terminal_size_x*(terminal_size_y+scrollback))
      selection_end=terminal_size_x*(terminal_size_y+scrollback);

    /* And scrap the selection if these are the same */
    if (selection_start==selection_end)
      selection_session=NULL;
  }

  reopen_and_size_window(session, old_terminal_size.x, old_terminal_size.y, old_scrollback,
                         redraw.r_charw, redraw.r_charh, eig);

  if (use_naws)
  {
    switch (session->connection_type)
    {
      case NETTLE_TELNET:
        if (session->session_flags[TELOPT_NAWS])
        {
          /* NAWS is on */
          char block[9];

          block[0]=TELNET_IAC;
          block[1]=TELNET_SB;
          block[2]=TELOPT_NAWS;
          block[3]=session->terminal_size.x / 256;
          block[4]=session->terminal_size.x % 256;
          block[5]=session->terminal_size.y / 256;
          block[6]=session->terminal_size.y % 256;
          block[7]=TELNET_IAC;
          block[8]=TELNET_SE;
          nettle_senddata(session, block, 9);
        }

        break;
    }
  }
}

void reopen_and_size_window(struct session_struct *session, int old_terminal_size_x,
                            int old_terminal_size_y, int old_scrollback,
                            int old_charw, int old_charh, struct coords old_eig)
{
  {
    struct wimp_setextent_block block;

    block.min.x=0;
    block.min.y=-((session->terminal_size.y+session->scrollback)*redraw.r_charh << eig.y);
    block.max.x=session->terminal_size.x*redraw.r_charw << eig.x;
    block.max.y=0;

    _swi(Wimp_SetExtent, _INR(0,1), session->window_handle, &block);

    if (session->line_editor_type!=LINEEDIT_NONE)
    {
      block.min.x=0;
      block.min.y=-48;
      block.max.x=session->terminal_size.x*redraw.r_charw << eig.x;
      block.max.y=0;

      _swi(Wimp_SetExtent, _INR(0,1), session->pane_handle, &block);
    }
  }

  {
    struct wimp_getwindowstate_block block;

    block.window_handle=session->window_handle;
    _swi(Wimp_GetWindowState, _IN(1), &block);

    /* If terminal at fullsize, then reopen window at fullsize, otherwise leave it */
    if (block.max.x-block.min.x==(old_terminal_size_x*old_charw << old_eig.x))
    {
      block.max.x=block.min.x+(session->terminal_size.x*redraw.r_charw << eig.x);
    }
    if (block.max.y-block.min.y==(old_terminal_size_y*old_charh << old_eig.y))
    {
      block.min.y=block.max.y-(session->terminal_size.y*redraw.r_charh << eig.y);
    }

    /* If terminal is bigger than fullsize in the new font shrink it down to fullsize */
    if (block.max.x-block.min.x>(session->terminal_size.x*redraw.r_charw << old_eig.x))
    {
      block.max.x=block.min.x+(session->terminal_size.x*redraw.r_charw << eig.x);
    }
    if (block.max.y-block.min.y>(session->terminal_size.y*redraw.r_charh << old_eig.y))
    {
      block.min.y=block.max.y-(session->terminal_size.y*redraw.r_charh << eig.y);
    }

    /* If scrollback has changed, then shift the scroll offset */
    if (old_scrollback!=session->scrollback)
    {
      block.scroll.y-=(session->scrollback-old_scrollback)*old_charh << eig.y;
    }

    if (block.max.x > screensize.x)
    {
      block.min.x -= (block.max.x-screensize.x);
      block.max.x -= (block.max.x-screensize.x);
    }

    if (block.max.y > screensize.y)
    {
      block.min.y -= (block.max.y-screensize.y);
      block.max.y -= (block.max.y-screensize.y);
    }

    /* Scale the scroll offset according to the old font size/new font size */
    block.scroll.x=(block.scroll.x*(redraw.r_charw << eig.x)/(old_charw << old_eig.x));
    block.scroll.y=(block.scroll.y*(redraw.r_charh << eig.y)/(old_charh << old_eig.y));

    /* wimp_openwindow_block starts the same way as wimp_getwindowstate_block */
    _swi(Wimp_OpenWindow, _IN(1), &block);

    force_redraw(session->window_handle,0,
                 (-session->terminal_size.y-session->scrollback)*
                 redraw.r_charh << eig.y,
                        session->terminal_size.x*redraw.r_charw << eig.x,0);

    if (session->line_editor_type!=LINEEDIT_NONE)
      open_pane_window(session, (struct wimp_openwindow_block *) &block);
  }
}

void scroll_term (struct session_struct *session, int direction)
{
  struct wimp_getwindowstate_block block;

  block.window_handle = session->window_handle;
  _swi (Wimp_GetWindowState, _IN(1), (int) &block);
  block.scroll.y += direction * (block.max.y - block.min.y);
  _swi (Wimp_OpenWindow, _IN(1), (int) &block);
}

void nettle_reconnect(struct session_struct *session)
{
  switch (session->connection_type)
  {
    case NETTLE_TELNET:
    {
      /* This is probably nasty for anything but telnet */
      int i;

      if (session->socket_handle!=-1)
      {
        socket_close(session->socket_handle);
      }

      for (i=0; i<256; i++)
      {
        session->session_flags[i]=false;
      }

      session->socket_state=NETTLE_SESSION_RESOLVE;

      if (session->dns)
      {
        dns_dispose(session->dns);
        session->dns=NULL;
      }

      session->dns = dns_gethostbyname(session->socket_host);
      assert(session->dns != NULL);

      set_title_bar(session->window_handle, lookup_static ("title_conn"));
      write_out_strings (session,
                         lookup_static_var ("lookup", 1,
                                             session->socket_host),
                         "\r\n", 0);
      main_requirenull = true;

      break;
    }
  }
}
