#include <string.h>
#include <signal.h>
#include <curses.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#define VERSION "1.0"

char *helptext[] = {
    "Left/Up/Right/Down - move",
    "Space - Toggle",
    "L - Load file",
    "S - Save file",
    "C - Save font to console",
    "D - Load font from console",
    "Q - Quit",
    "R - redraw screen",
    "PgUp - previous char",
    "PgDown - next char",
    "# - jump to specific char"
};
const int helplines = 11;

char *title = "fnteditfs "VERSION" - A Full-Screen Console Font Editor";

int charnum = 65;
int editx = 0, edity = 0;

WINDOW *helpwin, *editwin, *statusln;
char fontbuf[4096];
char fnamebuf[_POSIX_PATH_MAX+1];

#define edit_line_refresh \
    prefresh(pad, 0, x / (width - 10) * (width - 10) - 10, top, left, top, left + width)

char *edit_line (char *buf, int bufsize, int top, int left, int width, int numeric) {
    WINDOW *pad = newpad(1, bufsize);
    int ch, buflen = strlen(buf);
    int insertmode = 1;
    int y = 0, x = buflen;
    if (!pad)
	return (char*)0;
    keypad(pad, 1);
    wattrset(pad, A_BOLD);
    wclear(pad);
    waddstr(pad, buf);
    edit_line_refresh;
    while (1) {
	getyx(pad, y, x);
	ch = wgetch(pad);
	switch (ch) {
        case -1:
	    return NULL;
	case KEY_IC: // insert
	    insertmode = !insertmode;
	    break;
	case KEY_DC: // delete
	    if (buflen == 0) {
		beep();
		edit_line_refresh;
		break;
	    }
	    if (x == buflen)
		wmove(pad, y, --x);
	    memcpy(&buf[x], &buf[x+1], buflen - x + 1);
	    buf[--buflen] = (char)0;
	    waddstr(pad, &buf[x]);
	    wclrtoeol(pad);
	    wmove(pad, y, x);
	    edit_line_refresh;
	    break;
	case KEY_BACKSPACE: // backspace
	    if (buflen == 0 || x == 0) {
		beep();
		edit_line_refresh;
		break;
	    }
	    if (x == buflen) {
		wmove(pad, y, --x);
        	waddch(pad, ' ');
		wmove(pad, y, x);
        	buflen--;
	    } else {
		wmove(pad, y, --x);
		memcpy(&buf[x], &buf[x+1], buflen - x + 1);
		buf[--buflen] = (char)0;
		waddstr(pad, &buf[x]);
		wclrtoeol(pad);
		wmove(pad, y, x);
	    }
            edit_line_refresh;
	    break;
	case KEY_HOME: // home
	    x = 0;
	    wmove(pad, y, x);
            edit_line_refresh;
	    break;
	case KEY_END: // end
	    x = buflen;
	    wmove(pad, y, x);
            edit_line_refresh;
	    break;
	case KEY_LEFT: // left arrow
	    if (!x) { 
		beep();
                edit_line_refresh;
		break;
	    }
	    x--;
	    wmove(pad, y, x);
            edit_line_refresh;
	    break;
	case KEY_RIGHT: // right arrow
	    if (x == buflen) { 
		beep();
                edit_line_refresh;
		break;
	    }
	    x++;
	    wmove(pad, y, x);
            edit_line_refresh;
	    break;
	case 12: // ^L char
	    redrawwin(pad);
            edit_line_refresh;
	    break;
	case '\r': // CR
	case '\n': // LF (line feed)
	    wattrset(pad, 0);
	    wmove(pad, 0, 0);
	    wclear(pad);
            edit_line_refresh;
	    delwin(pad);
	    buf[buflen] = (char)0;
	    return (char*)buf;
	case 21: // ^U char
	    x = 0;
	    buflen = 0;
	    wclear(pad);
            edit_line_refresh;
	    break;
	case 27: // ESC
	    wattrset(pad, 0);
	    wmove(pad, 0, 0);
	    wclear(pad);
            edit_line_refresh;
	    delwin(pad);
	    return (char*) 0;
	default: // any other key
	    if ((ch > 255 || ch < 32) || (numeric && (ch < '0' || ch > '9'))) {
		beep();
                edit_line_refresh;
		break;
	    }
	    if (buflen >= bufsize - 1) { // end of buffer
		beep();
                edit_line_refresh;
		break;
	    }
	    if (x == buflen) {
	        buf[x] = ch;
    		waddch(pad, ch);
        	buflen++;
	    } else {
	        if (insertmode) {
		    memcpy(&buf[x+1], &buf[x], buflen - x);
	    	    buf[++buflen] = (char)0;
		    buf[x] = ch;
	    	    waddstr(pad, &buf[x]);
	    	    wmove(pad, 0, ++x);
		} else {
	    	    buf[x] = ch;
            	    waddch(pad, ch);
		}
	    }
            edit_line_refresh;
    	}
    }
}

void draw_char (WINDOW *win, int num, char buf[]) {
    int i;
    wmove(win, 0, 12 / 2 - 5);
    wprintw(win, " Char %03d ", charnum);
    for (i = 0; i < 128; i++) {
	if (i % 8 == 0)
	    wmove(win, i / 8 + 1, 2);
	waddch(win, buf[i / 8] & (1 << (7 - i % 8)) ? ACS_BLOCK : ' ');
    }
    wmove(win, edity + 1, editx + 2);
}

void toggle_char_bit(WINDOW *win, int num, char *buf, int y, int x) {
    buf[num * 16 + y] ^= (1 << (7 - x));
    wmove(win, y + 1, x + 2);
    waddch(win, buf[num * 16 + y] & (1 << (7 - x)) ? ACS_BLOCK : ' ');
    wmove(win, y + 1, x + 2);
}

void draw_screen () {
    int i, len = strlen(title);
    attrset(A_STANDOUT);
    move(0, 0);
    for (i = 0; i < (80 - len) / 2; i++)
	addch(' ');
    addstr(title);
    for (i = (80 - len) / 2 + len; i < COLS; i++)
	addch(' ');
    helpwin = newwin(helplines + 2, 30, 5, 5);
    box(helpwin, ACS_VLINE, ACS_HLINE);
    wmove(helpwin, 0, 30 / 2 - 3);
    waddstr(helpwin, " Keys ");
    for (i = 0; i < helplines; i++) {
	wmove(helpwin, 1 + i, 2);
	waddstr(helpwin, helptext[i]);
    }
    editwin = newwin(18, 12, 5, 59);
    box(editwin, ACS_VLINE, ACS_HLINE);
    draw_char(editwin, charnum, &fontbuf[charnum * 16]);
    refresh();
    statusln = newwin(1, COLS, LINES - 1, 0);
    wrefresh(helpwin);
    wrefresh(editwin);
}

void ask_move_char () {
    char buf[4];
    buf[0] = 0;
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "New character #:");
    wrefresh(statusln);
    if (edit_line(buf, sizeof(buf), LINES - 1, 17, 4, 1)) {
	charnum = atol(buf) % 256;
        draw_char(editwin, charnum, &fontbuf[charnum * 16]);
	wrefresh(editwin);
    }
    wclear(statusln);
    wrefresh(statusln);
    wrefresh(editwin);
}

void interactive_load_file () {
    char buf[sizeof(fnamebuf)];
    int fd, rc;
    strcpy(buf, fnamebuf);
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Load from file:");
    wrefresh(statusln);
    if (!edit_line(buf, sizeof(buf), LINES - 1, 16, COLS - 16, 0)) {
	wclear(statusln);
        mvwaddstr(statusln, 0, 0, "Load canceled.");
        wrefresh(statusln);
	wrefresh(editwin);
	return;
    }
    strcpy(fnamebuf, buf);
    fd = open(buf, O_RDONLY);
    if (fd < 0) {
	wclear(statusln);
        mvwprintw(statusln, 0, 0, "Load error: open: %s", strerror(errno));
        wrefresh(statusln);
	wrefresh(editwin);
	return;
    }
    rc = read(fd, fontbuf, sizeof(fontbuf));
    if (rc < sizeof(fontbuf)) {
	wclear(statusln);
	if (rc < 0)
            mvwprintw(statusln, 0, 0, "Load error: read: %s", strerror(errno));
	else if (rc == 0)
            mvwaddstr(statusln, 0, 0, "Load error: Font is empty");
	else
	    mvwaddstr(statusln, 0, 0, "Load warning: Font is too short; partially loaded");
        wrefresh(statusln);
        draw_char(editwin, charnum, &fontbuf[charnum * 16]);
	wrefresh(editwin);
        close(fd);
	return;
    }
    close(fd);
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Font loaded.");
    wrefresh(statusln);
    draw_char(editwin, charnum, &fontbuf[charnum * 16]);
    wrefresh(editwin);
}

void interactive_save_file () {
    char buf[sizeof(fnamebuf)];
    int fd, rc;
    strcpy(buf, fnamebuf);
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Save to file:");
    wrefresh(statusln);
    if (!edit_line(buf, sizeof(buf), LINES - 1, 14, COLS - 14, 0)) {
	wclear(statusln);
        mvwaddstr(statusln, 0, 0, "Save canceled.");
        wrefresh(statusln);
	wrefresh(editwin);
	return;
    }
    strcpy(fnamebuf, buf);
    fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
	wclear(statusln);
        mvwprintw(statusln, 0, 0, "Save error: open: %s", strerror(errno));
        wrefresh(statusln);
	wrefresh(editwin);
	return;
    }
    rc = write(fd, fontbuf, sizeof(fontbuf));
    if (rc < sizeof(fontbuf)) {
	wclear(statusln);
        mvwprintw(statusln, 0, 0, "Save error: write: %s", strerror(errno));
        wrefresh(statusln);
	wrefresh(editwin);
        close(fd);
	return;
    }
    close(fd);
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Font saved.");
    wrefresh(statusln);
    wrefresh(editwin);
}

/**********************************/
/* operation system depended code */
/**********************************/

#if defined(__FreeBSD__)

#include <sys/ioctl.h>
#include <sys/consio.h>

int do_ioctl(const char *header, unsigned long request, char *buf) {
    int fd;
    
    if (ioctl(0, request, buf) < 0) {
	fd = open("/dev/console", O_RDONLY, 0);
	if (fd < 0) {
	    wclear(statusln);
    	    mvwprintw(statusln, 0, 0, "%s failed to open /dev/console: %s", header, strerror(errno));
	    wrefresh(statusln);
	    wrefresh(editwin);
	    return -1;
	}
	if (ioctl(fd, request, buf) < 0) {
	    wclear(statusln);
    	    mvwprintw(statusln, 0, 0, "%s failed to ioctl /dev/console: %s", header, strerror(errno));
	    wrefresh(statusln);
	    wrefresh(editwin);
	    close(fd);
	    return -1;
	}
    }
    return 0;
}

void save_font_to_console () {
    if (do_ioctl("Save error:", PIO_FONT8x16, fontbuf) < 0)
	return;

    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Font saved to console.");
    wrefresh(statusln);
    wrefresh(editwin);
}

void load_font_from_console () {
    if (do_ioctl("Load error:", GIO_FONT8x16, fontbuf) < 0)
	return;

    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "Font loaded from console.");
    wrefresh(statusln);
    draw_char(editwin, charnum, &fontbuf[charnum * 16]);
    wrefresh(editwin);
}

#else

void save_font_to_console () {
    wclear(statusln);
    mvwaddstr(statusln, 0, 0, "No console interaction is supported under this OS");
    wrefresh(statusln);
    wrefresh(editwin);
}

void load_font_from_console () {
    save_font_to_console();
}

#endif /* OS is not supported */

/*****************************************/
/* end of operation system depended code */
/*****************************************/

void input_loop () {
    int c;
    while (1) {
	c = getch();
	switch (c) {
	    case KEY_UP: 
		edity ? edity -- : (edity = 15);
		wmove(editwin, edity+1, editx+2);
		wrefresh(editwin);
		break;
	    case KEY_LEFT:
		editx ? editx -- : (editx = 7);
		wmove(editwin, edity+1, editx+2);
		wrefresh(editwin);
		break;
	    case KEY_DOWN:
		edity ++; edity %= 16;
		wmove(editwin, edity+1, editx+2);
		wrefresh(editwin);
		break;
	    case KEY_RIGHT:
		editx ++; editx %= 8;
		wmove(editwin, edity+1, editx+2);
		wrefresh(editwin);
		break;
	    case KEY_PPAGE:
		charnum ? charnum-- : (charnum = 255);
	        draw_char(editwin, charnum, &fontbuf[charnum * 16]);
		wrefresh(editwin);
		break;
	    case KEY_NPAGE:
		charnum++;
		charnum %= 256;
	        draw_char(editwin, charnum, &fontbuf[charnum * 16]);
		wrefresh(editwin);
		break;
	    case '#':
		ask_move_char();
		break;
	    case ' ':
		toggle_char_bit(editwin, charnum, fontbuf, edity, editx);
		wrefresh(editwin);
		break;
	    case 'l': case 'L':
		interactive_load_file();
		break;
	    case 's': case 'S':
		interactive_save_file();
		break;
	    case 'c': case 'C':
		save_font_to_console();
		break;
	    case 'd': case 'D':
		load_font_from_console();
		break;
	    case 'r': case 'R':
		redrawwin(stdscr);
		redrawwin(helpwin);
		redrawwin(editwin);
		refresh();
		wrefresh(helpwin);
	        wrefresh(editwin);
		break;
	    case 'q': case 'Q':
		return;
	}
    }
}

void print_usage (char *argv[]) {
    printf("Full screen font editor v" VERSION " by Uri Shaked <uri@keves.org>\n");
    printf("Usage: %s fontfile\n", argv[0]);
    exit(1);
}

int main (int argc, char *argv[]) {
    int fd, rc;
    if (argc > 2)
	print_usage(argv);
    memset(fontbuf, 0, sizeof(fontbuf));
    if (argc == 2) {
        strcpy(fnamebuf, argv[1]);
	fd = open(argv[1], O_RDONLY);
	if (fd == -1) {
	    perror("open");
	    return 1;
	}
        rc = read(fd, fontbuf, sizeof(fontbuf));
	if (rc == -1) {
	    perror("read");
	    return 1;
	}
	close(fd);
    } else
	fnamebuf[0] = 0;
    initscr();
    cbreak();
    noecho();
    intrflush(stdscr,FALSE);
    keypad(stdscr, TRUE);
    nonl();
    draw_screen();
    // if no font file loaded, try loading console font.
    if (fnamebuf[0] == 0)
        load_font_from_console();
    input_loop();
    endwin();
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1