/*
 This file is part of MOST.

 Copyright (c) 1991, 1999, 2002, 2005-2018, 2019 John E. Davis

 This program is free software; you can redistribute it and/or modify it
 under the terms of the GNU General Public License as published by the Free
 Software Foundation; either version 2 of the License, or (at your option)
 any later version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along
 with this program; if not, write to the Free Software Foundation, Inc., 675
 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "config.h"

#include <stdio.h>
#include <string.h>

#include <ctype.h>
#include <slang.h>
#include "jdmacros.h"

#include "most.h"
#include "line.h"
#include "window.h"
#include "display.h"

int Most_Tab_Width = 8;

int Most_Selective_Display = 0;
int Most_Show_Wrap_Marker = 1;

#define IS_BYTE_PRINTABLE(b) \
   ((((b) >= ' ') && ((b) < 0x7F)) \
       || ((Most_UTF8_Mode == 0) && ((b) >= SLsmg_Display_Eight_Bit)))

/* take 16 binary characters and put them in displayable form */
static void binary_format_line (unsigned char *beg, unsigned char *end,
			       char *buf)
{
   unsigned char *b;
   char *s, *s1;
   unsigned char ch;
   int count;

   count = 0;
   b = beg;
   s = buf;

   while (b < end)
     {
	if (count == 4)
	  {
	     *s++ = ' ';
	     count = 0;
	  }
	count++;

	ch = *b++;

	if ((Most_V_Opt == 0)
	    || (ch & 0x80))
	  {
	     sprintf (s, "%02X", ch);
	     s += 2;
	     continue;
	  }

	if ((ch >= ' ') && (ch < 0x7F))
	  {
	     *s++ = ' ';
	     *s++ = (char) ch;
	     continue;
	  }

	*s++ = '^';
	if (ch < ' ') ch += '@';
	else ch = '?';
	*s++ = ch;
     }

   s1 = buf + (9 * 4) + 4;
   while (s < s1)
     *s++ = ' ';

   b = beg;
   while (b < end)
     {
        ch = *b++;
	if (IS_BYTE_PRINTABLE(ch))
	  {
	     *s++ = ch;
	     continue;
	  }
	*s++ = '.';
     }
   *s = 0;
}

static void output_binary_formatted_line (void)
{
   unsigned char *beg, *end;
   char buf[256];

   beg = Most_Beg + Most_C_Offset;
   end = beg + 16;

   if (end > Most_Eob) end = Most_Eob;

   sprintf (buf, "0x%08lX: ", (unsigned long) Most_C_Offset);
   binary_format_line (beg, end, buf + 12);
   SLsmg_write_string (buf);
   SLsmg_erase_eol ();
}

/* Here *begp points to the char after \e.
 * The general escape sequence parsed here is assumed to look like:
 *   \e[ XX ; ... m
 * If 30 <= XX <= 37, then it specifies the foreground color
 * If 40 <= XX <= 47, then a background color is specified
 * If  0 <= XX <= 8, then an attribute (e.g, 8) is specified.
 * These numbers will be encoded as:
 *  offset + (FG-30 + 8*(BG-40 + 9*attribute))
 */
int most_parse_color_escape (unsigned char **begp, unsigned char *end, int *colorp)
{
   unsigned char *beg = *begp;
   int fg = 38, bg = 48, at = 0;
   int xx;

   if ((beg >= end) || (*beg != '['))
     return -1;

   beg++; /* skip [ */
#if 1
   if ((beg < end) && (*beg == 'K'))
     {
       if (colorp != NULL) *colorp = -1;
       *begp = beg + 1;
       return 0;
     }
#endif

   while (1)
     {
	xx = 0;
	while ((beg < end) && isdigit (*beg))
	  {
	     xx = xx*10 + (*beg - '0');
	     beg++;
	  }
	if ((xx >= 0) && (xx <= 8))
	  at = xx;
	else if ((xx >= 20) && (xx <= 28))
	  xx = 0;
	else if ((xx >= 30) && (xx <= 37))
	  fg = xx;
	else if ((xx >= 40) && (xx <= 47))
	  bg = xx;
	else return -1;

	if ((beg < end) && (*beg == ';'))
	  {
	     beg++;
	     continue;
	  }

	if ((beg < end) && ((*beg == 'm') || (*beg == ']')))
	  {
	     *begp = beg + 1;
	     if (colorp != NULL)
	       {
		  if ((fg != 38) || (bg != 48))
		    xx = ((fg-30) + 9*((bg-40) + 9*at));
		  if (xx != 0)
		    xx += MOST_EMBEDDED_COLOR_OFFSET;
		  *colorp = xx;
	       }
	     return 0;
	  }
	return -1;
     }
}

typedef struct
{
   unsigned char *bytes;
   unsigned char byte;		       /* used if bytes is NULL */
   unsigned int len;
   int color;
}
Multibyte_Cell_Type;

static int most_analyse_line (unsigned char *begg, unsigned char *endd,
			      Multibyte_Cell_Type *cells, unsigned int num_cols, int *start_colorp)
{
   unsigned char *beg, *end;
   unsigned int min_col, max_col, prev_width;
   unsigned int col, max_col_reached;
   int default_attr;
   Multibyte_Cell_Type *cell, *max_cell;

   beg = begg;
   end = endd;
   col = max_col_reached = 0;
   cell = cells;
   max_cell = cell;
   min_col = Most_Column - 1;
   max_col = min_col + num_cols;

   default_attr = *start_colorp;
   prev_width = 1;
   while (beg < end)
     {
	int attr = default_attr;
	unsigned char ch;
	unsigned char *pch = beg++;
	char buf[16];

	if ('\n' == (ch = *pch))
	  break;

	if ((ch == '\r') && (Most_V_Opt == 0))
	  {
	     if (col > max_col_reached) max_col_reached = col;
	     col = 0;
	     prev_width = 1;
	     continue;
	  }

	if ((ch == '\b') && (Most_V_Opt == 0))
	  {
	     if (col > max_col_reached) max_col_reached = col;
	     if (col < prev_width)
	       col = 0;
	     else
	       col -= prev_width;
	     continue;
	  }

	if (col < max_col_reached)		       /* overstrike */
	  {
	     attr = MOST_BOLD_COLOR;
	     if ((col >= min_col) && (col < max_col))
	       {
		  cell = cells + (col-min_col);
		  if (cell->bytes[0] == '_')
		    attr = MOST_ULINE_COLOR;
		  else if (ch == '_')
		    {
		       cell->color = MOST_ULINE_COLOR;
		       col++;
		       continue;
		    }
	       }
	     /* drop */
	  }

	if (IS_BYTE_PRINTABLE(ch))
	  {
	     if ((col >= min_col) && (col < max_col))
	       {
		  cell = cells + (col-min_col);
		  cell->bytes = pch;
		  cell->len = 1;
		  cell->color = attr;
		  if (cell >= max_cell)
		    max_cell = cell + 1;
	       }
	     col++;
	     prev_width = 1;
	     continue;
	  }

	if ((ch == '\t') && (Most_T_Opt == 0) && (Most_Tab_Width))
	  {
	     int nspaces = Most_Tab_Width * (col/Most_Tab_Width + 1) - col;
	     prev_width = nspaces;
	     while (nspaces > 0)
	       {
		  if ((col >= min_col) && (col < max_col))
		    {
		       cell = cells + (col-min_col);
		       cell->bytes = &cell->byte;
		       cell->byte = ' ';
		       cell->color = attr;
		       cell->len = 1;
		       if (cell >= max_cell)
			 max_cell = cell + 1;
		    }
		  col++;
		  nspaces--;
	       }
	     continue;
	  }
#if 1
	if ((ch == 033) && (Most_V_Opt == 0))
	  {
	     int color;
	     if (0 == most_parse_color_escape (&beg, end, &color))
	       {
		  if (color != -1) default_attr = color;
		  continue;
	       }
	     /* drop */
	  }
#endif

	if (ch & 0x80)
	  {
	     SLwchar_Type wch;
	     if ((Most_UTF8_Mode)
		 && (NULL != SLutf8_decode (pch, end, &wch, NULL)))
	       {
		  int width = SLwchar_wcwidth (wch);
		  beg = SLutf8_skip_chars (pch, end, 1, NULL, 1);

		  prev_width = width;
		  if (width == 0)
		    {
		       col--;
		       if ((col >= min_col) && (col < max_col))
			 {
			    cell = cells + (col-min_col);
			    cell->len += beg-pch;
			 }
		       col++;
		       continue;
		    }

		  if ((col >= min_col) && (col < max_col))
		    {
		       cell = cells + (col-min_col);
		       cell->bytes = pch;
		       cell->color = attr;
		       cell->len = beg - pch;
		       if (cell >= max_cell)
			 max_cell = cell + 1;
		    }
		  col++;
		  if (width > 1)
		    {
		       if ((col >= min_col) && (col < max_col))
			 {
			    cell = cells + (col-min_col);
			    cell->bytes = pch;
			    cell->color = attr;
			    cell->len = 0;
			    if (cell >= max_cell)
			      max_cell = cell + 1;
			 }
		       col++;
		    }
		  continue;
	       }

	     /* Otherwise, this displays as <XX> and takes up 4 character cells */
	     sprintf (buf, "<%02X>", (unsigned int) ch);
	     prev_width = 4;
	     /* drop */
	  }
	else
	  {
	     /* Otherwise we have a Ctrl-char displayed as ^X */
	     if (ch == 0x7F) ch = '?';
	     else ch += '@';

	     sprintf (buf, "^%c", ch);
	     prev_width = 2;
	  }

	pch = (unsigned char *)buf;
	while (*pch)
	  {
	     if ((col >= min_col) && (col < max_col))
	       {
		  cell = cells + (col-min_col);
		  cell->bytes = &cell->byte;
		  cell->byte = *pch;
		  cell->color = attr;
		  cell->len = 1;
		  if (cell >= max_cell)
		    max_cell = cell + 1;
	       }
	     col++;
	     pch++;
	  }
     }

   if (col < max_col_reached)
     col = max_col_reached;
   else
     max_col_reached = col;

   /* Now add "..." if selective display.  To do that, the next line needs to
    * be dealt with to determine whether or not it will be hidden.
    */
   if (Most_Selective_Display
       && (Most_W_Opt == 0)
       && (beg < Most_Eob)
       && ((col >= min_col) && (col < max_col)))
     {
	if (*beg == '\n') beg++;

	while ((beg < Most_Eob)
	       && ((*beg == ' ') || (*beg == '\t') || (*beg == '\r')))
	  beg++;

	if ((beg >= Most_Eob) || (*beg == '\n')
	    || (most_apparant_distance(beg) >= Most_Selective_Display))
	  {
	     max_col_reached = col + 3;
	     while (col < max_col_reached)
	       {
		  if (col < max_col)
		    {
		       cell = cells + (col-min_col);
		       cell->bytes = &cell->byte;
		       cell->byte = '.';
		       cell->color = 0;
		       cell->len = 1;
		       if (cell >= max_cell)
			 max_cell = cell + 1;
		    }
		  col++;
	       }
	  }
     }
   *start_colorp = default_attr;
   return max_cell - cells;
}

static void display_cells (Multibyte_Cell_Type *cell, unsigned int n, char dollar)
{
   Multibyte_Cell_Type *cell_max;
   int last_color = -1;

   cell_max = cell + n;
   while (cell < cell_max)
     {
	if (last_color != cell->color)
	  {
	     last_color = cell->color;
	     SLsmg_set_color (last_color);
	  }
	SLsmg_write_chars (cell->bytes, cell->bytes + cell->len);
	cell++;
     }

   if (last_color != 0)
     SLsmg_set_color (0);

   SLsmg_erase_eol ();
   if (dollar)
     {
	SLsmg_gotorc (SLsmg_get_row (), SLtt_Screen_Cols-1);
	SLsmg_write_nchars (&dollar, 1);
     }
}

void most_display_line (int reset)
{
   unsigned char *beg, *end;
   unsigned char dollar;
   static Multibyte_Cell_Type *cells;
   static unsigned int num_cells;
   unsigned int screen_cols;
   unsigned int num_cells_set;
   static int last_color = 0;	       /* used for a line that wrapped */

   if (Most_B_Opt)
     {
	output_binary_formatted_line ();
	return;
     }

   screen_cols = SLtt_Screen_Cols;
   if (num_cells != screen_cols + 1)
     {
	num_cells = screen_cols + 1;

	SLfree ((char *) cells);
	if (NULL == (cells = (Multibyte_Cell_Type *)SLcalloc (num_cells, sizeof (Multibyte_Cell_Type))))
	  most_exit_error ("Out of memory");
     }

   (void) most_extract_line (&beg, &end);

   if (reset || (Most_W_Opt == 0))
     last_color = 0;
   num_cells_set = most_analyse_line (beg, end, cells, num_cells, &last_color);

   dollar = 0;
   if (Most_W_Opt)
     {
	if (Most_Show_Wrap_Marker
	    && (end < Most_Eob)
	    && (*end != '\n'))
	  dollar = '\\';
     }
   else if (num_cells_set > screen_cols)
     dollar = '$';

   display_cells (cells, num_cells_set, dollar);
}

/* given a position in a line, return apparent distance from bol
   expanding tabs, etc... up to pos */
int most_apparant_distance (unsigned char *pos)
{
   int i, prev_width;
   unsigned char *save_pos, ch;
   unsigned int save_offset;

   save_offset = Most_C_Offset;
   save_pos = pos;
   Most_C_Offset = (unsigned int) (pos - Most_Beg);
   pos = most_beg_of_line();
   Most_C_Offset = save_offset;

   i = 0;
   prev_width = 1;
   while (pos < save_pos)
     {
	ch = *pos++;
	if (IS_BYTE_PRINTABLE(ch))
	  {
	     i++;
	     prev_width = 1;
	     continue;
	  }

	if ((ch == '\b') && (Most_V_Opt == 0))
	  {
	     i -= prev_width;
	     if (i < 0) i = 0;
	     continue;
	  }
	if ((ch == '\r') && (Most_V_Opt == 0))
	  {
	     if (i != 1) i = 0;
	     prev_width = 1;
	     continue;
	  }
	if ((ch == '\t') && (Most_T_Opt == 0))
	  {
	     prev_width = Most_Tab_Width * (i/Most_Tab_Width + 1) - i;  /* Most_Tab_Width column tabs */
	     i += prev_width;
	     continue;
	  }

	if ((ch == 033) && (Most_V_Opt == 0)
	    && (0 == most_parse_color_escape (&pos, save_pos, NULL)))
	  continue;

	if (ch & 0x80)
	  {
	     SLwchar_Type wch;
	     if ((Most_UTF8_Mode)
		 && (NULL != SLutf8_decode (pos-1, save_pos, &wch, NULL)))
	       {
		  prev_width = SLwchar_wcwidth (wch);
		  pos = SLutf8_skip_chars (pos-1, save_pos, 1, NULL, 1);
		  i += prev_width;
		  continue;
	       }
	     prev_width = 4;
	     i += prev_width;	       /* <XX> */
	     continue;
	  }

	prev_width = 2;
	i += prev_width;	       /* ^X */
     }
   return i;
}

/*
 * Returns a pointer to the num_cols'th character after the one
 * pointed at b. Invisible character runs are not counted toward this
 * limit, i.e. strings that represent attributes, such as "_\b" for
 * underlines.
 *
 * If multi_column is non-zero, characters spanning more than one
 * column will add their extra width to the column count.
 *
 * If there the end of the buffer is reached, as delimited by argument
 * e, then e is returned.
 */
unsigned char *most_forward_columns (unsigned char *b, unsigned char *e, unsigned int num_cols)
{
   unsigned int col = 0;
   unsigned int prev_width = 1;

   while (b < e)
     {
	unsigned char ch = *b++;

	if (col >=num_cols)
	  {
	     if ((ch == 033) && (Most_V_Opt == 0))
	       {
		  while ((ch == 033)
			 && (0 == most_parse_color_escape (&b, e, NULL))
			 && (b < e))
		    ch = *b++;
	       }
	     b--;
	     break;
	  }

	if (IS_BYTE_PRINTABLE(ch))
	  {
	     col++;
	     prev_width = 1;
	     continue;
	  }

	if (ch & 0x80)
	  {
	     SLwchar_Type wch;

	     if ((Most_UTF8_Mode)
		 && (NULL != SLutf8_decode (b-1, e, &wch, NULL)))
	       {
		  b = SLutf8_skip_chars (b-1, e, 1, NULL, 1);
		  prev_width = SLwchar_wcwidth (wch);
		  col += prev_width;
		  continue;
	       }
	     prev_width = 4;
	     col += prev_width;	       /* <XX> */
	     continue;
	  }

	if (ch == '\b')
	  {
	     if (Most_V_Opt == 0)
	       {
		  if (col < prev_width)
		    col = 0;
		  else
		    col -= prev_width;
	       }
	     else col += 2;	       /* ^H */
	     continue;
	  }

	if (ch == '\r')
	  {
	     if (Most_V_Opt == 0)
	       {
		  prev_width = 1;
		  col = 0;
	       }
	     else col += 2;	       /* ^M */
	     continue;
	  }

	if (ch == '\t')
	  {
	     if (Most_T_Opt == 0)
	       {
		  prev_width = Most_Tab_Width * (col/Most_Tab_Width + 1) - col;
		  col += prev_width;
	       }
	     else
	       col += 2;	       /* ^I */
	     continue;
	  }

	if ((ch == 033) && (Most_V_Opt == 0)
	    && (0 == most_parse_color_escape (&b, e, NULL)))
	  continue;

	/* Ctrl-char ^X */
	prev_width = 2;
	col += prev_width;
     }
   return b;
}
