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

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 <malloc.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 "tty.h"
#include "termcap.h"
#include "window.h"
#include "config.h"


#define max(a, b) ((a) >= (b) ? (a) : (b))
#define min(a, b) ((a) <= (b) ? (a) : (b))


#define MAX_KEYS		2048

#define BUILTIN_OPERATIONS	8


#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 MAX_BUILTIN_NAME	15

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


int SCREEN_X, SCREEN_Y;

#define VIEWER_FIELDS 12

static char VIEWERFields[VIEWER_FIELDS][40] = 
{
    "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
static int VIEWERColors[VIEWER_FIELDS] = 
{
    BLACK, WHITE, OFF, WHITE, BLACK, ON, WHITE, BLACK, OFF, BLACK, WHITE, OFF
};
#endif

#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 LinuxConsole = 1;
int ColorMonitor = 1;
#else
int LinuxConsole = 0;
int ColorMonitor = 0;
#endif


extern char tty_type[];


char homedirectory[PATH_MAX];
char configfile[PATH_MAX] = "";
char cSection[]  = "[UITVIEW-Color]";
char bwSection[] = "[UITVIEW-Monochrome]";

char *header_text;
configuration *config;
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.2b - Hex/Ascii File Viewer";
static char UitViewModeHelp[256];
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)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, title_text, strlen(title_text));

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

    window_cursormove(title_win, 0, 0);
    window_write(temp, SCREEN_X);
}


void setheader(void)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, header_text, min(strlen(header_text), SCREEN_X));

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

    window_cursormove(header_win, 0, 0);
    window_write(temp, SCREEN_X);
}


void setstatus(void)
{
    char temp[256];

    memset(temp, ' ', SCREEN_X);
    memcpy(temp, UitViewModeHelp, strlen(UitViewModeHelp));

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

    window_cursormove(status_win, 0, 0);
    window_write(temp, SCREEN_X);	
}


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


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

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

    if ((r = read(handle, ln, 16)))
	sprintf(temp, "  %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  \n",
		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(temp + 13 + r * 3, ' ', (16 - r) * 3 - 1);
	memset(temp + 13 + 16 * 3 - 1 + 2 + r, ' ', 16 - r + 2);
    }

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


void update_all(void)
{
    int i;
    
    tty_foreground(ScreenForeground);
    tty_background(ScreenBackground);
    tty_bright(ScreenBrightness);

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


void clean_up(void)
{
    if (header_text)
    {
	free(header_text);
	header_text = NULL;
    }

    tty_end();
    tty_clrscr();
}


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


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

    switch (signum)
    {
	case SIGHUP:

	    clean_up();
	    /* :-) */
	    fprintf(stderr, "Got SIGHUP !\n");
	    break;

	case SIGINT:

	    clean_up();
	    break;

	case SIGTERM:
	case SIGQUIT:

	    clean_up();
	    fprintf(stderr, "Got %s !\nbye\n",
	    	   (signum == SIGTERM) ? "SIGTERM" : "SIGQUIT");
	    break;

	case SIGSEGV:

	    tty_end();
	    tty_clrscr();
	    fprintf(stderr, "Got SIGSEGV !\n");
	    fprintf(stderr, "Please report to tudor@ulise.cs.pub.ro\n");
	    break;
    }

    exit(1);
}


int main(int argc, char *argv[])
{
    struct stat s;
    int need_update;
    struct key_struct *ks;
    int key, repeat_count, size;
    char *home, *data = NULL, *section;
    char tmp[256], buf[256], offset[16];
    int sectionptr = 0, i, j, index, cnt = 0;


    termcap_init();
    tty_kbdinit(0);

    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(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, 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 is neither regular file nor block device.\n",
		argv[1]);
	exit(1);
    }

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

    regular_file = S_ISREG(s.st_mode);

    if (home = getenv("HOME"))
    {
        strcpy(homedirectory, home);
        strcpy(configfile, home);
        if (configfile[strlen(configfile) - 1] != '/') strcat(configfile, "/");
    }

    strcat(configfile, ".uitrc.");
    strcat(configfile, tty_type);

    config = configuration_init(configfile);

    if (configuration_getstatus(config) == STATUS_OK)
    {
	sectionptr = configuration_getsectionptr(config,"[Setup]");
	if (sectionptr == -1)
	    puts("[Setup] section missing.");
	else
	{
#ifdef HAVE_LINUX
	    configuration_getfielddata(config, sectionptr, "LinuxConsole",
				       &data, 1, DO_SEEK);
	    LinuxConsole = data && strcmp(data, "ON") == 0;
	    configuration_getfielddata(config, sectionptr, "ColorMonitor",
				       &data, 1, DO_SEEK);
	    ColorMonitor = data && strcmp(data, "ON") == 0;
#endif
	}

	sectionptr = configuration_getsectionptr(config, "[UITVIEW-Setup]");
        if (sectionptr == -1)
	    fprintf(stderr, "[UITVIEW-Setup] section missing.");
	else
	{
	    configuration_getfielddata(config, sectionptr,
				       "UitViewModeHelp",
				       &data, 1, DO_SEEK);
	    if (data) strncpy(UitViewModeHelp, data, 256);
	}

	section = (LinuxConsole && ColorMonitor) ? cSection : bwSection;

	sectionptr = configuration_getsectionptr(config, section);
	if (sectionptr == -1)
	    fprintf(stderr, "%s section missing.", section);
	else
	    for (i = 0; i < VIEWER_FIELDS; i++)
	    {
		configuration_getfielddata(config, sectionptr, VIEWERFields[i],
					   &data, 1, DO_SEEK);
		if (!data || (index  = tty_getcolorindex(data))== -1)
		    fprintf(stderr, "Invalid %s (%s).\n", VIEWERFields[i],
			    data);
		else
		    VIEWERColors[i] = index;
	    }

        sectionptr = configuration_getsectionptr(config, "[UITVIEW-Keys]");
        if (sectionptr == -1)
	    fprintf(stderr, "[UITVIEW-Keys] section missing.");
	else
	{
	    char key_seq[16];
	    char *contents;

	    for (i = 0; i < MAX_KEYS; i++)
	    {
	        configuration_getfielddata(config, sectionptr, 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, "Invalid builtin operation: %s\n",
			    contents);
	    }

	    if (i == MAX_KEYS)
	        fprintf(stderr,
			"Too many key sequences. Only %d are allowed.\n",
			MAX_KEYS);
	}
	configuration_end(config);
    }
    else
    {
        fprintf(stderr, "\nCannot open configuration file %s\n", configfile);
	fprintf(stderr, "See the manual page for details.\n");
	fprintf(stderr, "If your TERM environment variable is, let's say,\n");
	fprintf(stderr, "vt102, your configuration file name should be\n");
	fprintf(stderr, ".uitrc.vt102 . Try renaming .uitrc.console ...\n");
	exit(1);
    }

    tty_getsize(&SCREEN_X, &SCREEN_Y);

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

    header_text = (char *)malloc(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_init();

    offset[cnt]  = 0;
    current_line = 0;

restart:

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

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

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

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

    window_cursormove(screen_win, 20, 0);
    window_write(seek_txt, sizeof(seek_txt));

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

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

    update_all();
    window_cursormove(screen_win, 20, strlen(seek_txt));
    window_write(offset, 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_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(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(screen_win, 20, strlen(seek_txt) + cnt);
		window_write(" ", 1);
	        break;

	    case KEY_ENTER:

		if (cnt == 0)
		    tty_beep();
		else
		{
		    offset[cnt] = 0;
		    sscanf(offset, "%x", &cnt);
		    window_cursormove(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;
}
