/**
 * Selection code
 * (C) Nettle developers 2000-2001
 *
 * $Id: seln,v 1.44 2003/10/26 19:24:22 ijeffray Exp $
 */

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

#include "messages.h"
#include "misc.h"
#include "nettle.h"
#include "process.h"
#include "seln.h"
#include "socket.h"
#include "wimp.h"
#include "wimputil.h"
#include "zapredraw.h"



static const struct session_struct  *old_session;
static int                           old_start;
static int                           old_end;



static void selection_changedbox_init(void)
{
  old_session = selection_session;
  old_start   = MIN(selection_start, selection_end);
  old_end     = MAX(selection_start, selection_end);
}



static void selection_changedbox_redraw(const struct session_struct *session, int start, int end)
{
  struct coords from, to;

  from.x = start % session->terminal_size.x;
  from.y = start / session->terminal_size.x;
  to.x   = end   % session->terminal_size.x;
  to.y   = end   / session->terminal_size.x;

  if (from.y == to.y)
  {
    /* redraw part of a line */
    force_redraw (session->window_handle,
                  from.x * redraw.r_charw << eig.x,
                  (-to.y - 1) * redraw.r_charh << eig.y,
                  (to.x+1)* redraw.r_charw << eig.x,
                  -to.y * redraw.r_charh << eig.y);
  }
  else
  {
    /* redraw whole lines */
    force_redraw (session->window_handle,
                  0, ((-to.y - 1) * redraw.r_charh) << eig.y,
                  session->terminal_size.x * redraw.r_charw << eig.x,
                  -from.y * redraw.r_charh << eig.y);
  }
}



static void selection_changedbox_draw (void)
{
  int new_start = MIN(selection_start, selection_end);
  int new_end   = MAX(selection_start, selection_end);

  if (old_session != selection_session || old_start == old_end)
    selection_changedbox_redraw (selection_session, new_start, new_end);
  else
  {
    if (new_start < old_start)
      selection_changedbox_redraw (selection_session, new_start, old_start);
    else if (new_start > old_start)
      selection_changedbox_redraw (selection_session, old_start, new_start);

    if (new_end < old_end)
      selection_changedbox_redraw (selection_session, new_end, old_end);
    else if (new_end > old_end)
      selection_changedbox_redraw (selection_session, old_end, new_end);
  }
}


void selection_click(int x, int x_pos, int y, int y_pos)
{
  int click_point;
  int start;
  int end;

  start = MIN(selection_start, selection_end);
  end   = MAX(selection_start, selection_end);

  click_point = -( ( (y - y_pos) >> eig.y ) / redraw.r_charh )
                * selection_session->terminal_size.x
                + ( ( (x - x_pos) >> eig.x ) / redraw.r_charw );

  if (click_point < start || click_point > end)
  {
    /* click is not within selection */
    force_redraw_selection();
    selection_session = NULL;
  }
}




void selection_adjust(int x,int x_pos, int y, int y_pos)
{
  int click_point;
  int selection_middle;

  if (selection_session == NULL)
    return;

  click_point = -( ( (y - y_pos) >> eig.y ) / redraw.r_charh )
                * selection_session->terminal_size.x
                + ( ( (x - x_pos) >> eig.x ) / redraw.r_charw );

  click_point = MAX(click_point, 0);
  click_point = MIN(click_point,
                      selection_session->terminal_size.x*
                      (selection_session->terminal_size.y+
                                 selection_session->scrollback));

  selection_changedbox_init();

  selection_middle = (selection_start + selection_end) / 2;
  if (click_point < selection_middle)
  {
    /* the click is either before the selection, or inside the first half
     * of it, so we move the start [or, rather, whichever is actually earlier
     * of the start/end] */
    if (selection_start < selection_end)
      selection_adjust_dragging = SELECTION_ADJUST_START;
    else
      selection_adjust_dragging = SELECTION_ADJUST_END;
  }
  else
  {
    /* need to drag end of selection */
    if (selection_start > selection_end)
      /* selection_start is actually the 'end' of selection */
      selection_adjust_dragging=SELECTION_ADJUST_START;
    else
      selection_adjust_dragging=SELECTION_ADJUST_END;
  }

  if (selection_adjust_dragging == SELECTION_ADJUST_START)
    selection_start = click_point;
  else
    selection_end = click_point;

  selection_changedbox_draw();
}



static void selection_adjust_drag(int x,int x_pos, int y, int y_pos)
{
  if (selection_session == NULL)
    return;

  if (selection_session)
  {
    int click_point = -( ( (y - y_pos) >> eig.y ) / redraw.r_charh )
                      * selection_session->terminal_size.x
                      + ( ( (x - x_pos) >> eig.x ) / redraw.r_charw );

    if (click_point<0)
    {
      click_point=0;
    }
    if (click_point>(selection_session->terminal_size.x*
				(selection_session->terminal_size.y+
					selection_session->scrollback)))
    {
      click_point=(selection_session->terminal_size.x*
				(selection_session->terminal_size.y+
					selection_session->scrollback));
    }

    selection_changedbox_init();

    switch (selection_adjust_dragging)
    {
      case SELECTION_ADJUST_START:
	selection_start=click_point;
	break;
      case SELECTION_ADJUST_END:
	selection_end=click_point;
	break;
    }

    selection_changedbox_draw();
  }
}



void save_selection(char *filename, bool ansi_colour)
{
  FILE *file_handle;
  int loop;
  int current_fg=7;
  int current_bg=0;
  int old_flags = 0;
  int loop_start;
  int loop_end;
  int last_valid_char=0;
  int xpos;
  int ypos=0;
  int old_ypos=-1;

  file_handle=fopen(filename,"wb");

  if (file_handle==NULL)
  {
    gensaveerror(NULL, "nocreate");
    return;
  }

  loop_start = MIN(selection_start, selection_end);
  loop_end   = MAX(selection_start, selection_end);

  for (loop=loop_start; loop<loop_end; loop++)
  {
    xpos=loop % selection_session->terminal_size.x;
    old_ypos=ypos;
    ypos=loop / selection_session->terminal_size.x;

    /* If we're at the beginning of a line */
    if (xpos==0)
    {
      /* If this isn't the first character we've processed */
      if (loop>loop_start)
      {
        /* If this isn't the first line */
        if (ypos>0)
        {
          /* If we exist */
          if (selection_session->line_flags)
          {
            /* and we didn't wrap onto this line and it was a newline-caused linefeed */
            if ( !(selection_session->line_flags[ypos-1] & NETTLE_LINE_WRAP) )
            {
              fputc('\n', file_handle);
            }
          }
        }
      }
    }

    if (ypos!=old_ypos)
    {
      last_valid_char=selection_session->terminal_size.x-1;

      /* if this line wasn't linewrapped, then strip the spaces, otherwise keep them */
      if ( !(selection_session->line_flags[ypos] & NETTLE_LINE_WRAP) )
      {
        while (last_valid_char>=0 &&
          read_assigned_character(selection_session, last_valid_char, ypos)==' ')
        {
          last_valid_char--;
        }
      }
    }

    if (ansi_colour)
    {
      int flags = read_assigned_flags(selection_session, xpos, ypos);
      int transitions = flags ^ old_flags;
      int new_fg = read_assigned_fg(selection_session, xpos, ypos);
      int new_bg = read_assigned_bg(selection_session, xpos, ypos);

      /* transitions contains the flags which have changed between the last
         character and this character. We check whether a transition has
         taken place for each flag and, where it has, we write out the
         code based on the new state, held in flags. */

      if (transitions & NETTLE_FLAG_INVERTED)
        fprintf(file_handle,"\x1B[%s",flags & NETTLE_FLAG_INVERTED ? "7m" : "27m");
      if (transitions & NETTLE_FLAG_UNDERLINE)
        fprintf(file_handle,"\x1B[%s",flags & NETTLE_FLAG_UNDERLINE ? "4m" : "24m");
      if (transitions & NETTLE_FLAG_BLINK)
        fprintf(file_handle,"\x1B[%s",flags & NETTLE_FLAG_BLINK ? "5m" : "25m");
      old_flags = flags;

      if (new_fg != current_fg)
      {
        if (new_fg>7)
          fprintf(file_handle, "\x1B[" "1;3%cm", new_fg-8+48);
        else
          fprintf(file_handle, "\x1B[" "22;3%cm", new_fg+48);
        current_fg = new_fg;
      }

      if (new_bg != current_bg)
      {
        if (new_bg>7)
          fprintf(file_handle,"\x1B[" "1;4%cm", new_bg-8+48);
        else
          fprintf(file_handle,"\x1B[" "22;4%cm", new_bg+48);
        current_bg = new_bg;
      }
    }

    if (xpos<=last_valid_char)
    {
      fputc(read_assigned_character(selection_session, xpos, ypos), file_handle);
    }

    if (ferror(file_handle))
      break;
  }

  if (ferror(file_handle))
    gensaveerror (NULL, "nocreate");
  else
  {
    if (fclose(file_handle))
      gensaveerror(NULL, "nocreate");
    else
      misc_setfiletype(filename, 0xFFF);
  }
}



void handle_selection_drag(void)
{
  int x, y, buttons, window_handle, icon_handle;
  int x_pos, y_pos;
  bool do_scroll;

  {
    struct wimp_getpointerinfo_block block;

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

    x            =block.pos.x;
    y            =block.pos.y;
    buttons      =block.buttons;
    window_handle=block.window_handle;
    icon_handle  =block.icon_handle;
  }

  if (buttons==0)
  {
    if (selection_start==selection_end)
    {
      selection_session = NULL;
    }
    selection_in_progress=false;
    return;
  }

  {
    struct wimp_getwindowinfo_block block;

    block.window_handle=selection_session->window_handle;

    _swi(Wimp_GetWindowInfo, _IN(1), ((int) &block)+1);

    x_pos=block.min.x-block.scroll.x;
    y_pos=block.max.y-block.scroll.y;

    /* Scroll when selection happening and we're near the edge of the window */
    do_scroll = false;

    if (x<(block.min.x+32))
    {
      block.scroll.x-=(block.min.x+32-x);
      do_scroll = true;
    }

    if (x>(block.max.x-32))
    {
      block.scroll.x+=(x-block.max.x+32);
      do_scroll = true;
    }

    if (y<(block.min.y+32))
    {
      block.scroll.y-=(block.min.y+32-y);
      do_scroll = true;
    }

    if (y>(block.max.y-32))
    {
      block.scroll.y+=(y-block.max.y+32);
      do_scroll = true;
    }

    if (do_scroll)
    {
      _swi(Wimp_OpenWindow, _IN(1), &block);
    }

    if ( x<block.min.x )
      x = block.min.x;

    if ( x>block.max.x )
      x = block.max.x;
  }

  switch (buttons)
  {
    case 4:
      /* Drag SELECT - clear the selection, re-work it out, and set it again, then redraw */
      selection_changedbox_init();

      selection_end = -( ( (y - y_pos) >> eig.y ) / redraw.r_charh )
                      * selection_session->terminal_size.x
                      + ( ( (x - x_pos) >> eig.x ) / redraw.r_charw );

      if (selection_end>(selection_session->terminal_size.x*
				(selection_session->terminal_size.y+
					selection_session->scrollback)))
      {
	selection_end=(selection_session->terminal_size.x*
				(selection_session->terminal_size.y+
					selection_session->scrollback));
      }
      if (selection_end<0)
      {
	selection_end=0;
      }

      selection_changedbox_draw();

      break;
    case 1:
      /* ADJUST dragging */
      selection_adjust_drag(x,x_pos,y,y_pos);
      break;
  }
}



void force_redraw_selection (void)
{
  if (selection_session)
  {
    old_session = 0;
    selection_changedbox_draw ();
  }
}



void selection_paste(struct session_struct *session)
{
  char   *buffer;
  int     bufferptr=0;
  int     loop_start;
  int     loop_end;
  int     loop;
  int     last_valid_char=0;
  int     xpos;
  int     ypos=0;
  int     old_ypos=-1;

  if (selection_session == NULL)
    return;

  loop_start = MIN(selection_start, selection_end);
  loop_end   = MAX(selection_start, selection_end);

  buffer=malloc(loop_end-loop_start+
                ((loop_end-loop_start)/selection_session->terminal_size.x)+3);

  if ( !buffer )
    return;

  _swi(Hourglass_On, 0);

  for (loop=loop_start; loop<loop_end; loop++)
  {
    xpos=loop % selection_session->terminal_size.x;
    old_ypos=ypos;
    ypos=loop / selection_session->terminal_size.x;

    /* If we're at the beginning of a line */
    if (xpos==0)
    {
      /* If this isn't the first character we've processed */
      if (loop>loop_start)
      {
        /* If this isn't the first line */
        if (ypos>0)
        {
          /* If we exist */
          if (selection_session->line_flags)
          {
            /* and we didn't wrap onto this line and it was a newline-caused linefeed */
            if ( !(selection_session->line_flags[ypos-1] & NETTLE_LINE_WRAP) )
            {
              *(buffer+bufferptr) = '\r';
              bufferptr++;
            }
          }
        }
      }
    }

    if (ypos!=old_ypos)
    {
      last_valid_char=selection_session->terminal_size.x-1;

      /* if this line wasn't linewrapped, then strip the spaces, otherwise keep them */
      if ( !(selection_session->line_flags[ypos] & NETTLE_LINE_WRAP) )
      {
        while (last_valid_char>=0 &&
          read_assigned_character(selection_session, last_valid_char, ypos)==' ')
        {
          last_valid_char--;
        }
      }
    }

    if (xpos<=last_valid_char)
    {
      *(buffer+bufferptr) = read_assigned_character(selection_session, xpos, ypos);
      bufferptr++;
    }
  }

  for (loop=0; loop<bufferptr; loop+=1024)
  {
    if ((bufferptr-loop)>1024)
      process_data_transmission(session, buffer+loop, 1024);
    else
      process_data_transmission(session, buffer+loop, bufferptr-loop);
  }

  _swi(Hourglass_Off, 0);

  free(buffer);
}
