/*
    File hex/ascii viewer utility
    Copyright (c) 1993, 1994 Tudor Hulubei & Andrei Pitis

This file is part of UIT (UNIX Interactive Tools)

UIT 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, or (at your option) any later version.

UIT 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
UIT; see the file COPYING.  If not, write to the Free Software Foundation,
675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <termios.h>
#include <sys/stat.h>

#include "xmalloc.h"
#include "tty.h"
#include "window.h"
#include "config.h"
#include "tilde.h"
#include "misc.h"


#define MAX_KEYS		2048

#define BUILTIN_OPERATIONS	 9


#define BUILTIN_CursorUp	-1
#define BUILTIN_CursorDown	-2
#define BUILTIN_PageUp		-3
#define BUILTIN_PageDown        -4
#define BUILTIN_Home		-5
#define BUILTIN_End		-6
#define BUILTIN_Refresh		-7
#define BUILTIN_Exit		-8
#define BUILTIN_HardRefresh	-9


#define MAX_BUILTIN_NAME	15

char built_in[BUILTIN_OPERATIONS][MAX_BUILTIN_NAME] =
{
    "<CursorUp>",
    "<CursorDown>",
    "<PageUp>",
    "<PageDown>",
    "<Home>",
    "<End>",
    "<Refresh>",
    "<Exit>",
    "<HardRefresh>",
};


int SCREEN_X, SCREEN_Y;

#define VIEWER_FIELDS 12

static char *ViewerFields[VIEWER_FIELDS] =
{
    "TitleForeground",
    "TitleBackground",
    "TitleBrightness",
    "HeaderForeground",
    "HeaderBackground",
    "HeaderBrightness",
    "ScreenForeground",
    "ScreenBackground",
    "ScreenBrightness",
    "StatusForeground",
    "StatusBackground",
    "StatusBrightness"
};

#ifdef HAVE_LINUX
static int ViewerColors[VIEWER_FIELDS] = 
{
    CYAN, BLUE, ON, CYAN, RED, ON, BLACK, CYAN, OFF, CYAN, BLUE, ON
};
#else	/* not HAVE_LINUX */
static int ViewerColors[VIEWER_FIELDS] = 
{
    BLACK, WHITE, OFF, WHITE, BLACK, ON, WHITE, BLACK, OFF, BLACK, WHITE, OFF
};
#endif	/* not HAVE_LINUX */

#define TitleForeground			ViewerColors[0]
#define TitleBackground			ViewerColors[1]
#define TitleBrightness			ViewerColors[2]
#define HeaderForeground		ViewerColors[3]
#define HeaderBackground		ViewerColors[4]
#define HeaderBrightness		ViewerColors[5]
#define ScreenForeground		ViewerColors[6]
#define ScreenBackground		ViewerColors[7]
#define ScreenBrightness		ViewerColors[8]
#define StatusForeground		ViewerColors[9]
#define StatusBackground		ViewerColors[10]
#define StatusBrightness		ViewerColors[11]


#ifdef HAVE_LINUX
int AnsiColorSequences = ON;
#else	/* not HAVE_LINUX */
int AnsiColorSequences = OFF;
#endif	/* not HAVE_LINUX */



char *home;
char *program;
char *tty_name;
int  tty_name_len;
char *header_text;
char *global_buf = NULL;
char cSection[]  = "[UITVIEW-Color]";
char bwSection[] = "[UITVIEW-Monochrome]";
int handle, regular_file;
int current_line, lines;
window *title_win, *header_win, *screen_win, *status_win;
static char title_text[]=
    " UNIX Interactive Tools 4.3.1 - Hex/Ascii File Viewer";
static char *UitViewModeHelp;
static char info_txt[] =
    "   Offset    00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F        \
Ascii       ";
static char line_txt[]	  =
    "  ------------------------------------------------------------------\
----------  ";
static char seek_txt[]	  = "  Seek at: ";


int filelength(void)
{
    int temp, length;

    if (!regular_file) return 0x7FFFFFFF;
    temp = tell(handle);
    lseek(handle, 0, SEEK_END);
    length = tell(handle);
    lseek(handle, temp, SEEK_SET);
    return length;
}


void settitle(void)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, title_text, strlen(title_text));

    tty_bright(TitleBrightness);
    tty_foreground(TitleForeground);
    tty_background(TitleBackground);

    window_cursormove_notify(title_win, 0, 0);
    window_write(global_buf, SCREEN_X);
}


void setheader(void)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, header_text, min(strlen(header_text), SCREEN_X));

    tty_bright(HeaderBrightness);
    tty_foreground(HeaderForeground);
    tty_background(HeaderBackground);

    window_cursormove_notify(header_win, 0, 0);
    window_write(global_buf, SCREEN_X);
}


void setstatus(void)
{
    memset(global_buf, ' ', SCREEN_X);
    memcpy(global_buf, UitViewModeHelp, strlen(UitViewModeHelp));

    tty_bright(StatusBrightness);
    tty_foreground(StatusForeground);
    tty_background(StatusBackground);

    window_cursormove_notify(status_win, 0, 0);
    window_write(global_buf, SCREEN_X);	
}


char char_to_print(char c)
{
    return is_print(c) ? c : '.';
}


void update_line(int line)
{
    int r;
    unsigned char ln[16];

    memset(ln, 0, 16);
    memset(global_buf, ' ', SCREEN_X);
    lseek(handle, line * 16, SEEK_SET);

    if ((r = read(handle, ln, 16)))
	sprintf(global_buf, "  %07X0   %02X %02X %02X %02X %02X %02X %02X\
 %02X %02X %02X %02X %02X %02X %02X %02X %02X  %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c  ",
		line,
		ln[0], ln[1], ln[2],  ln[3],  ln[4],  ln[5],  ln[6],  ln[7],
		ln[8], ln[9], ln[10], ln[11], ln[12], ln[13], ln[14], ln[15],
		char_to_print(ln[0]),  char_to_print(ln[1]),
		char_to_print(ln[2]),  char_to_print(ln[3]),
		char_to_print(ln[4]),  char_to_print(ln[5]),
		char_to_print(ln[6]),  char_to_print(ln[7]),
		char_to_print(ln[8]),  char_to_print(ln[9]),
		char_to_print(ln[10]), char_to_print(ln[11]),
		char_to_print(ln[12]), char_to_print(ln[13]),
		char_to_print(ln[14]), char_to_print(ln[15]));

    if (r != 16)
    {
	memset(global_buf + 13 + r * 3, ' ', (16 - r) * 3 - 1);
	memset(global_buf + 13 + 16 * 3 - 1 + 2 + r, ' ', 16 - r + 2);
    }

    window_write((char *)global_buf, SCREEN_X);
}


void update_all(void)
{
    int i;
    
    tty_cursor(OFF);

    tty_bright(ScreenBrightness);
    tty_foreground(ScreenForeground);
    tty_background(ScreenBackground);

    for (i = current_line; i < current_line + 16; i++)
    {
        window_cursormove_notify(screen_win, 3 + i - current_line, 0);
	update_line(i);
    }

    tty_cursor(ON);
}


void clean_up(void)
{
    tty_exit();
}


void fatal(char *postmsg)
{
    clean_up();
    fprintf(stderr, "%s: fatal error: %s.\n", program, postmsg);
    exit(1);
}


void panic(int signum)
{
    tcflush(0, TCIOFLUSH);
    clean_up();

    switch (signum)
    {
	case SIGHUP:

	    /* :-) */
	    fprintf(stderr, "%s: got SIGHUP.\n", program);
	    break;

	case SIGINT:

	    break;

	case SIGTERM:
	case SIGQUIT:

	    fprintf(stderr, "%s: got %s.\n",
		    program, ((signum == SIGTERM) ? "SIGTERM" : "SIGQUIT"));
	    break;

	case SIGSEGV:

	    fprintf(stderr, "%s: got SIGSEGV.\n", program);
	    fprintf(stderr, "%s: please report to tudor@ulise.cs.pub.ro.\n",
	    	    program);
	    break;
    }

    exit(1);
}


int main(int argc, char *argv[])
{
    struct stat s;
    char *contents;
    int need_update;
    char key_seq[16];
    char offset[16];
    char *data = NULL;
    struct key_struct *ks;
    int i, j, index, cnt = 0;
    int key, repeat_count, size, first_time = 1;


    program = argv[0];

    home = getenv("HOME");
    if (!home) home = ".";

    get_tty_name();
    get_login_name();

    tty_name_len = strlen(tty_name);

    tty_get_capabilities();
    tty_kbdinit(TTY_RESTRICTED_INPUT);

    signal(SIGTERM, panic);
    signal(SIGINT , panic);
    signal(SIGQUIT, panic);
    signal(SIGSEGV, panic);
    signal(SIGHUP,  panic);

    signal(SIGILL,  SIG_IGN);
    signal(SIGTRAP, SIG_IGN);
    signal(SIGABRT, SIG_IGN);
    signal(SIGUSR1, SIG_IGN);
    signal(SIGUSR2, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    signal(SIGCONT, SIG_IGN);
    signal(SIGALRM, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGFPE,  SIG_IGN);

    if (argc != 2)
    {
	fprintf(stderr, "usage: uitview filename\n");
	exit(1);
    }

    stat(argv[1], &s);

    if (!(S_ISREG(s.st_mode) || S_ISBLK(s.st_mode)))
    {
	fprintf(stderr, "%s: %s is neither regular file nor block device.\n",
		program, argv[1]);
	exit(1);
    }

    handle = open(argv[1], O_RDONLY);

    if (handle == -1)
    {
	fprintf(stderr, "%s: cannot open file %s.\n", program, argv[1]);
	exit(1);
    }

    regular_file = S_ISREG(s.st_mode);

    configuration_check();


    use_section("[Setup]");

    AnsiColorSequences = get_flag_var("AnsiColorSequences", OFF);


    use_section("[UITVIEW-Setup]");

    UitViewModeHelp = get_string_var("UitViewModeHelp", "");

    use_section(AnsiColorSequences ? cSection : bwSection);

    get_colorset_var(ViewerColors, ViewerFields, VIEWER_FIELDS);


    use_section("[UITVIEW-Keys]");

    for (i = 0; i < MAX_KEYS; i++)
    {
        configuration_getvarinfo(key_seq, &contents, 1, NO_SEEK);

	if (*key_seq == 0) break;

	if (contents == NULL) continue;

        for (j = 0; j < BUILTIN_OPERATIONS; j++)
            if (strcmp(contents, built_in[j]) == 0)
                break;

	if (j < BUILTIN_OPERATIONS)
	{
	    if (tty_key_convert(key_seq))
	        tty_key_list_insert(key_seq, (void *) -(j + 1));
	}
	else
	    fprintf(stderr, "%s: invalid built-in operation: %s.\n",
		    program, contents);
    }

    if (i == MAX_KEYS)
        fprintf(stderr,	"%s: too many key sequences; only %d are allowed.\n",
		program, MAX_KEYS);

    configuration_end();

    tty_getsize(&SCREEN_X, &SCREEN_Y);
    tty_startup();

    if (SCREEN_Y < 24)
    {
	SCREEN_Y = 24;
	fprintf(stderr, "%s: WARNING: can't use less than 24 columns.\n",
		program);
    }

    global_buf  = xmalloc(SCREEN_X + 1);

    header_text = xmalloc(strlen(argv[1]) + 10);
    sprintf(header_text, " File: %s", argv[1]);

    title_win  = window_init(0, 0, 	      1,	    SCREEN_X);
    header_win = window_init(0, 1, 	      1,	    SCREEN_X);
    screen_win = window_init(0, 2,            SCREEN_Y - 2, SCREEN_X);
    status_win = window_init(0, SCREEN_Y - 1, 1,	    SCREEN_X);

    tty_set_mode(TTY_NONCANONIC);

    offset[cnt]  = 0;
    current_line = 0;


restart:

    if (first_time)
    {
	tty_bright(ScreenBrightness);
	tty_foreground(ScreenForeground);
	tty_background(ScreenBackground);
	tty_clrscr();
	first_time = 0;
    }

    settitle();
    setstatus();
    setheader();

    tty_bright(ScreenBrightness);
    tty_foreground(ScreenForeground);
    tty_background(ScreenBackground);

    window_cursormove_notify(screen_win, 1, 0);
    window_write(info_txt, sizeof(info_txt) - 1);
    window_cursormove_notify(screen_win, 2, 0);
    window_write(line_txt, sizeof(line_txt) - 1);

    size = filelength();
    lines = size / 16 + (size % 16 ? 1 : 0);

    current_line = min(current_line, (lines / 16) * 16);

    update_all();

    window_cursormove_notify(screen_win, 20, 0);
    window_write(seek_txt, sizeof(seek_txt) - 1);
    window_cursormove_notify(screen_win, 20, sizeof(seek_txt) - 1);
    window_write(offset, cnt);

    window_cursormove(screen_win, 20, sizeof(seek_txt) - 1 + cnt);

    while (1)
    {
	ks  = tty_getkey(&repeat_count);
	key = (int)ks->aux_data;
	if (key == 0) key = ks->key_seq[0];

	size = filelength();
	lines = size / 16 + (size % 16 ? 1 : 0);

	switch (key)
	{
	    case BUILTIN_CursorUp:

		need_update = 0;

	        while (repeat_count--)
	        {
	    	    if (current_line == 0) break;
		    current_line--, need_update = 1;
	        }

	        if (need_update) update_all();
	        break;

	    case BUILTIN_CursorDown:

		need_update = 0;

	        while (repeat_count--)
	        {
	    	    if (current_line >= lines - 16) break;
		    current_line++, need_update = 1;
	    	}

		if (need_update) update_all();
	        break;

	    case BUILTIN_PageUp:

	        if (current_line == 0) break;
	        current_line = max(0, current_line - 16);
	        update_all();
	    	break;

	    case BUILTIN_PageDown:

	        if (current_line >= lines - 16) break;
	        current_line += 16;
		update_all();
	    	break;

	    case BUILTIN_Home:

	        if (current_line)
	        {
	            current_line = 0;
	            update_all();
	        }
	        break;

	    case BUILTIN_End:

	        if (regular_file && current_line < lines - 16)
	        {
	            current_line = lines - 16;
	            update_all();
	        }
	        break;

	    case BUILTIN_HardRefresh: 

		first_time = 1;

	    case BUILTIN_Refresh: 

	    	goto restart;

	    case '0': case '1': case '2': case '3': case '4': case '5':
	    case '6': case '7': case '8': case '9': case 'A': case 'B':
	    case 'C': case 'D': case 'E': case 'F': case 'a': case 'b':
	    case 'c': case 'd': case 'e': case 'f':
	        if (cnt < 8)
	        {
		    window_cursormove_notify(screen_win, 20,
		    			     strlen(seek_txt) + cnt);
		    window_write((char *)&key, 1);
	            offset[cnt++] = key;
	        }
	        else
	            tty_beep();
		break;

	    case key_BACKSPACE:

	        if (cnt) cnt --;
		window_cursormove_notify(screen_win, 20,
					 strlen(seek_txt) + cnt);
		window_putch(' ');
	        break;

	    case key_ENTER:

		if (cnt == 0)
		    tty_beep();
		else
		{
		    offset[cnt] = 0;
		    sscanf(offset, "%x", &cnt);
		    window_cursormove_notify(screen_win, 20, strlen(seek_txt));
		    window_write("        ", 8);
		    if (cnt < 0)    cnt = 0;
		    if (cnt > size) cnt = size;
		    current_line = cnt >> 4;
		    update_all();
	            cnt = 0;
		}
	        break;

	    case BUILTIN_Exit:
	        goto end;
	}

	window_cursormove(screen_win, 20, strlen(seek_txt) + cnt);
    }

  end:
    
    clean_up();
    return 0;
}
