/* This file pager demo illustrates the screen management and 
 * keyboard routines.
 */

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

#include <stdlib.h>

#include <signal.h>

#ifdef unix
# include <unistd.h>
#endif

#include <slang.h>

#include "demolib.c"

static void usage (char *pgm)
{
   fprintf (stderr, "Usage: %s [FILENAME]\n", pgm);
   exit (1);
}

static int read_file (char *);
static void main_loop (void);

static char *File_Name;		       /* if NULL, use stdin */

int main (int argc, char **argv)
{  
   if (argc == 2)
     {
	File_Name = argv[1];
     }
   else if ((argc != 1) || (1 == isatty (fileno(stdin))))
     usage (argv[0]);

   
   if (-1 == read_file (File_Name))
     {
	fprintf (stderr, "Unable to read %s\n", File_Name);
	return 1;
     }
   
   /* This sets up the terminal, signals, screen management routines, etc... */
   if (-1 == demolib_init_terminal (1, 1))
     {
	fprintf (stderr, "Unable to initialize terminal.");
	return 1;
     }
   
#define APP_KEY_EOB  0x1001
#define APP_KEY_BOB  0x1002
   
   /* Add a few application defined keysyms.  0x1000 and above are for 
    * applications.
    */
   (void) SLkp_define_keysym ("\033>", APP_KEY_EOB);
   (void) SLkp_define_keysym ("\033<", APP_KEY_BOB);
   
   main_loop ();		       /* should not return */
   return 1;
}

   
/* The SLscroll routines will be used for pageup/down commands.  They assume
 * a linked list of lines.  The first element of the structure MUST point to 
 * the NEXT line, the second MUST point to the PREVIOUS line.
 */
typedef struct _File_Line_Type
{
   struct _File_Line_Type *next;
   struct _File_Line_Type *prev;
   char *data;			       /* pointer to line data */
}
File_Line_Type;

static File_Line_Type *File_Lines;

/* The SLscroll routines will use this structure. */
static SLscroll_Window_Type Line_Window;

static void free_lines (void)
{
   File_Line_Type *line, *next;
   
   line = File_Lines;
   while (line != NULL)
     {
	next = line->next;
	if (line->data != NULL) free (line->data);
	free (line);
	line = next;
     }
   File_Lines = NULL;
}

static File_Line_Type *create_line (char *buf)
{
   File_Line_Type *line;
   
   line = (File_Line_Type *) malloc (sizeof (File_Line_Type));
   if (line == NULL) return NULL;
   
   memset ((char *) line, sizeof (File_Line_Type), 0);
   
   line->data = SLmake_string (buf);   /* use a slang routine */
   if (line->data == NULL)
     {
	free (line);
	return NULL;
     }
   
   return line;
}


static int read_file (char *file)
{
   FILE *fp;
   char buf [1024];
   File_Line_Type *line, *last_line;
   unsigned int num_lines;
   
   if (file == NULL) 
     fp = stdin;
   else fp = fopen (file, "r");
   
   if (fp == NULL) return -1;
   
   last_line = NULL;
   num_lines = 0;
   
   while (NULL != fgets (buf, sizeof(buf), fp))
     {
	num_lines++;
	
	if (NULL == (line = create_line (buf)))
	  {
	     fprintf (stderr, "Out of memory.");
	     free_lines ();
	     return -1;
	  }
	
	if (last_line == NULL)
	  File_Lines = line;
	else 
	  last_line->next = line;
	
	line->prev = last_line;
	line->next = NULL;
	
	last_line = line;
     }
   
   memset ((char *)&Line_Window, 0, sizeof (SLscroll_Window_Type));
   
   Line_Window.current_line = (SLscroll_Type *) File_Lines;
   Line_Window.lines = (SLscroll_Type *) File_Lines;
   Line_Window.line_num = 1;
   Line_Window.num_lines = num_lines;
   /* Line_Window.border = 3; */
   
   return 0;
}


static void update_display (void)
{
   unsigned int row, nrows;
   File_Line_Type *line;

   /* All well behaved applications should block signals that may affect
    * the display while performing screen update.
    */
   SLsig_block_signals ();
   
   Line_Window.nrows = nrows = SLtt_Screen_Rows - 1;

   /* Always make the current line equal to the top window line. */
   if (Line_Window.top_window_line != NULL)
     Line_Window.current_line = Line_Window.top_window_line;

   SLscroll_find_top (&Line_Window);
   
   row = 0;
   line = (File_Line_Type *) Line_Window.top_window_line;
   
   SLsmg_normal_video ();
   
   while (row < Line_Window.nrows)
     {
	SLsmg_gotorc (row, 0);
	
	if (line != NULL) 
	  {
	     SLsmg_write_string (line->data);
	     line = line->next;
	  }
	SLsmg_erase_eol ();
	row++;
     }
   
   SLsmg_gotorc (row, 0);
   SLsmg_reverse_video ();
   SLsmg_printf ("%s", (File_Name == NULL) ? "<stdin>" : File_Name);
   SLsmg_erase_eol ();
   SLsmg_refresh ();
   
   SLsig_unblock_signals ();
}

static int Screen_Start;

static void main_loop (void)
{
   int screen_start;

   while (1)
     {
	update_display ();
	switch (SLkp_getkey ())
	  {
	   case SL_KEY_ERR:
	   case 'q':
	   case 'Q':
	     demolib_exit (0);
	     break;
	     
	   case SL_KEY_RIGHT:
	     Screen_Start += 1;
	     screen_start = Screen_Start;
	     SLsmg_set_screen_start (NULL, &screen_start);
	     break;
	     
	   case SL_KEY_LEFT:
	     Screen_Start -= 1;
	     if (Screen_Start < 0) Screen_Start = 0;
	     screen_start = Screen_Start;
	     SLsmg_set_screen_start (NULL, &screen_start);
	     break;

	   case SL_KEY_UP:
	     SLscroll_prev_n (&Line_Window, 1);
	     Line_Window.top_window_line = Line_Window.current_line;
	     break;
	     
	   case '\r':
	   case SL_KEY_DOWN:
	     SLscroll_next_n (&Line_Window, 1);
	     Line_Window.top_window_line = Line_Window.current_line;
	     break;
	     
	   case SL_KEY_NPAGE:
	   case ' ': case 4:
	     SLscroll_pagedown (&Line_Window);
	     break;

	   case SL_KEY_PPAGE:
	   case 127: case 21:
	     SLscroll_pageup (&Line_Window);
	     break;

	   case APP_KEY_BOB:
	     while (-1 != SLscroll_pageup (&Line_Window))
	       ;
	     break;
	     
	   case APP_KEY_EOB:
	     while (-1 != SLscroll_pagedown (&Line_Window))
	       ;
	     break;
	     
	   default:
	     SLtt_beep ();
	  }
     }
}
