/*
    File hex/ascii viewer utility
    Copyright (c) Tudor Hulubei & Andrei Pitis, April 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))


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

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.1c - Hex/Ascii File Viewer";
static char status_text[] = " Arrows/PgUp/PgDown/Home/End-MOVE  r-REFRESH  ^C/F10-QUIT";
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, status_text, strlen(status_text));

    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 (c >= ' ' && 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;
    int key, repeat_count, size;
    char *home, *data = NULL, *section;
    char tmp[256], buf[256], offset[16];
    int sectionptr = 0, i, index, cnt = 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");

    config = configuration_init(configfile);
 
    if (configuration_getstatus(config) == STATUS_OK)
    {
	if ((sectionptr = configuration_getsectionptr(config, "[Setup]")) == -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
	}

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

	if ((sectionptr = configuration_getsectionptr(config, section)) == -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;
	    }
	configuration_end(config);
    }
    else
	fprintf(stderr, "Cannot open configuration file %s.\nUsing defaults.\n", configfile);

    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 = 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);

    termcap_init();
    tty_kbdinit();
    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 ((key = tty_getkey(&repeat_count)), 1)
    {
	size = filelength();
	lines = size / 16 + (size % 16 ? 1 : 0);
        if (isupper(key)) key = tolower(key);

	switch (key)
	{
	    case -(188 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^y */
	    case -TTY_CURSOR_UP:
		need_update = 0;
	        while (repeat_count--)
	        {
	    	    if (current_line == 0) break;
		    current_line--, need_update = 1;
	        }
	        if (need_update) update_all();
	        break;

	    case -(22 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^b */
	    case -TTY_CURSOR_DOWN:
		need_update = 0;
	        while (repeat_count--)
	        {
	    	    if (current_line >= lines - 16) break;
		    current_line++, need_update = 1;
	    	}
		if (need_update) update_all();
	        break;

	    case '-':
	    case -TTY_CURSOR_LEFT:
	    case -TTY_PGUP:
	        if (current_line == 0) break;
	        current_line = max(0, current_line - 16);
	        update_all();
	    	break;

	    case '+':
	    case -TTY_CURSOR_RIGHT:
	    case -TTY_PGDN:
	        if (current_line >= lines - 16) break;
	        current_line += 16;
		update_all();
	    	break;

	    case -(72 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^ku */
	    case -TTY_HOME:
	        if (current_line)
	        {
	            current_line = 0;
	            update_all();
	        }
	        break;

	    case -(73 + TTY_FIRST_CONFIG_KEY):		/* hard coded ^kv */
	    case -TTY_END:
	        if (regular_file && (current_line != (lines / 16) * 16))
	        {
	            current_line = (lines / 16) * 16;
	            update_all();
	        }
	        break;

	    case 'r': 
	    	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 -TTY_F10:
	        goto end;
	}
	window_cursormove(screen_win, 20, strlen(seek_txt) + cnt);
    }

  end:
    
    clean_up();
    return 0;
}
