/*
 * Potential future TODO items. Points marked ISSUE need to be
 * resolved one way or another, with good justification for the
 * decision made, before implementation begins.
 * 
 *  - Multiple buffers, multiple on-screen windows.
 *     + ^X^F to open new file
 *     + ^X^R to open new file RO
 *     + ^X b to switch buffers in a window
 *     + ^X o to switch windows
 *     + ^X 2 to split a window
 *     + ^X 1 to destroy all windows but this
 *     + ^X 0 to destroy this window
 *     + ^X ^ to enlarge this window by one line
 *     + width settings vary per buffer (aha, _that's_ why I wanted
 * 	 a buffer structure surrounding the raw B-tree)
 *     + hex-editor-style minibuffer for entering search terms,
 * 	 rather than the current rather crap one; in particular
 * 	 this enables pasting into the search string.
 *     + ISSUE: how exactly do we deal with the problem of saving
 * 	 over a file which we're maintaining references to in
 * 	 another buffer? The _current_ buffer can at least be
 * 	 sorted out by replacing it with a fresh tree containing a
 * 	 single file-data block, but other buffers are in trouble.
 * 	  * if we can rely on Unix fd semantics, one option is just
 * 	    to keep the fd open on the original file, and then the
 * 	    data stays around even after we rename(2) our new
 * 	    version over the top. Disk space usage gets silly after
 * 	    a few iterations, but it's better than nothing.
 * 
 *  - Undo!
 *     + this actually doesn't seem _too_ horrid. For a start, one
 * 	 simple approach would be to clone the entire buffer B-tree
 * 	 every time we perform an operation! That's actually not
 * 	 _too_ expensive, if we maintain a limit on the number of
 * 	 operations we may undo.
 *     + I had also thought of cloning the tree we insert for each
 * 	 buf_insert_data and cloning the one removed for each
 * 	 buf_delete_data (both must be cloned for an overwrite),
 * 	 but I'm not convinced that simply cloning the entire thing
 * 	 isn't a superior option.
 *     + this really starts to show up the distinction between a
 * 	 `buffer' and a bare tree. A buffer is something which has
 * 	 an undo chain attached; so, in particular, the cut buffer
 * 	 shouldn't be one. Sort that out.
 * 
 *  - In-place editing.
 *     + this is an extra option useful for editing disk devices
 * 	 directly (!), or other situation in which it's impossible
 * 	 or impractical to rename(2) your new file over the old
 * 	 one. It causes a change of semantics when saving: instead
 * 	 of constructing a new backup file and writing it over the
 * 	 old one, we simply seek within the original file and write
 * 	 out all the pieces that have changed.
 *     + Saving the file involves identifying the bits of the file
 * 	 that need to change, and changing them. A piece of file
 * 	 can be discarded as `no change required' if it's
 * 	 represented in the buffer by a from-file block whose file
 * 	 offset is equal to its offset in the buffer.
 * 	  * Once we have identified all the bits that do need to
 * 	    change, we have to draw up a dependency graph to
 * 	    indicate which bits want to be copied from which other
 * 	    bits. (You don't want to overwrite a piece of file if
 * 	    you still have from-file blocks pointing at that
 * 	    piece.) This is a directed graph with nodes
 * 	    corresponding to intervals of the file, and edges
 * 	    indicating that the source node's interval is intended
 * 	    to end up containing the data from the target node's
 * 	    interval in the original file. Another node type is
 * 	    `literal data', which can be the target of an edge but
 * 	    never the source.
 * 	     - note that this means any two nodes connected by an
 * 	       edge must represent intervals of the same length.
 * 	       Sometimes this means that an interval must be split
 * 	       into pieces even though it is represented in the
 * 	       buffer by a single large from-file block (if
 * 	       from-file blocks copying _from_ it don't cover the
 * 	       whole of it). I suspect the simplest approach here
 * 	       is just to start by making a B-tree of division
 * 	       points in the file: every from-file block adds four
 * 	       division points (for start and end of both source
 * 	       and dest interval), and once the tree is complete,
 * 	       each graph node represents the interval between two
 * 	       adjacent division points.
 * 	     - ISSUE: actually, that strategy is inadequate:
 * 	       consider a large from-file block displaced by only
 * 	       one byte from its source location. The above
 * 	       strategy gives division points at x, x+1, x+y,
 * 	       x+y+1, but the interval [x,x+1] actually wants to
 * 	       point to [x+1,x+2] and we don't have a division
 * 	       point for that. Worse still, finding a way to add
 * 	       the remaining division points is also undesirable
 * 	       because there'd be so many of them. Needs design
 * 	       changes.
 * 	  * Then, any node which is not the target of any edge
 * 	    represents a piece of file which it's safe to write
 * 	    over, so we do so and throw away the node.
 * 	  * If we run out of such nodes and the graph is still
 * 	    non-empty, it's because all remaining nodes are part of
 * 	    loops. A loop must represent a set of disjoint
 * 	    intervals in the file, all the same length, which need
 * 	    to be permuted cyclically. So we deal with such a loop
 * 	    by reading a chunk of data from the start of one of the
 * 	    intervals and holding it, then copying from the next
 * 	    interval to that one, and so on until we've gone round
 * 	    the loop.
 * 	     + the intervals in the loop might be far too big to
 * 	       hold an entire interval's worth of real data in
 * 	       memory, so we might have to do it piecewise.
 *     + ISSUE: I wonder if a warning of some sort might be in
 * 	 order for if you accidentally request most of the file be
 * 	 moved about. This sort of trickery is really intended for
 * 	 small changes to a large file; if you (say) enable insert
 * 	 mode while editing a hard disk and accidentally leave
 * 	 everything one byte further up, you _really_ don't want to
 * 	 hit Save. The semantics of the warning are difficult,
 * 	 though.
 *
 *  - Custom display and/or input formats?
 *     + for example, Zap on RISC OS is able to display a binary
 * 	 file at 4 bytes per line and show the ARM disassembly of
 * 	 each word. For added credit, ability to type an ARM
 * 	 instruction back _in_ and have it reassembled into binary
 * 	 would be even better.
 *     + a simpler example is that sometimes you want to view a
 * 	 file as a sequence of little-endian 32-bit words rather
 * 	 than single bytes.
 *     + this would have to involve some sort of scripting or
 * 	 internal API. I'd really rather the interface was nailed
 * 	 down very early on and people were then free to develop
 * 	 custom formats without my involvement; I might be
 * 	 persuaded to keep a library of them or a list of
 * 	 hyperlinks or something, but actually _maintaining_ them
 * 	 is more effort than I want.
 *     + ARM assembler is all very well, but what about x86, with
 * 	 its variable instruction length? You can start
 * 	 disassembling from any byte position and work forwards
 * 	 unambiguously, but going backwards or jumping to an
 * 	 arbitrary byte position is much harder. You might have to
 * 	 shift your current file view back or forward by one byte
 * 	 to resynchronise, and the semantics of insert mode become
 * 	 generally confused, and even trying to _predict_ what a
 * 	 sensible synchronisation point would be when jumping to a
 * 	 bit of the file you've never seen before ... yuck.
 * 	  * The key thing that makes this horrid is that the custom
 * 	    display mode looks at the file _contents_, not merely
 * 	    its length, when deciding how many bytes per line to
 * 	    display. File-position-dependent number of bytes per
 * 	    line is fine, but _data_ dependency is doom.
 * 	  * So I think that in the interests of not causing tension
 * 	    between random things people would like in _some_ hex
 * 	    editor and what makes Tweak Tweak, I am going to put my
 * 	    foot down and say that I will not implement any
 * 	    mechanism which permits a data-dependent number of
 * 	    bytes per line. Anything short of that, fine, send me a
 * 	    patch or a detailed and well thought out design and
 * 	    I'll consider it on its merits.
 * 	  * I don't, OTOH, see any reason why a custom display
 * 	    function couldn't be permitted to see data before or
 * 	    after the current lineful if it wanted to. So x86
 * 	    disassembly could be done in a one-byte-per-line sort
 * 	    of fashion in which each line shows the machine
 * 	    instruction which the CPU would see if it started
 * 	    executing at that byte, and also gave its length. Then
 * 	    you could pick out the sequence of instructions you
 * 	    were interested in from the various out-of-sync ones.
 */

#include "tweak.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#if defined(unix) && !defined(GO32)
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
#elif defined(MSDOS)
#include <dos.h>
#include <process.h>
#endif

static void init(void);
static void done(void);
static void load_file (char *);

char toprint[256];		       /* LUT: printable versions of chars */
char hex[256][3];		       /* LUT: binary to hex, 1 byte */

char message[80];

char decstatus[] = "%s TWEAK "VER": %-18.18s %s posn=%-10"OFF"d size=%-10"OFF"d";
char hexstatus[] = "%s TWEAK "VER": %-18.18s %s posn=0x%-8"OFF"X size=0x%-8"OFF"X";
char *statfmt = hexstatus;

char last_char;
char *pname;
char *filename = NULL;
buffer *filedata, *cutbuffer = NULL;
int fix_mode = FALSE;
int look_mode = FALSE;
int eager_mode = FALSE;
int insert_mode = FALSE;
int edit_type = 1;		       /* 1,2 are hex digits, 0=ascii */
int finished = FALSE;
int marking = FALSE;
int modified = FALSE;
int new_file = FALSE;		       /* shouldn't need initialisation -
					* but let's not take chances :-) */
fileoffset_t width = 16;
fileoffset_t realoffset = 0, offset = 16;

int ascii_enabled = TRUE;

fileoffset_t file_size = 0, top_pos = 0, cur_pos = 0, mark_point = 0;

int scrlines;

/*
 * Main program
 */
int main(int argc, char **argv) {
    fileoffset_t newoffset = -1, newwidth = -1;

    /*
     * Parse command line arguments
     */
    pname = *argv;		       /* program name */
    if (argc < 2) {
	fprintf(stderr,
		"usage: %s [-f] [-l] [-e] filename\n"
		"    or %s -D to write default tweak.rc to stdout\n",
		pname, pname);
	return 0;
    }

    while (--argc > 0) {
	char c, *p = *++argv, *value;

	if (*p == '-') {
	    p++;
	    while (*p) switch (c = *p++) {
	      case 'o': case 'O':
	      case 'w': case 'W':
		/*
		 * these parameters require arguments
		 */
		if (*p)
		    value = p, p = "";
		else if (--argc)
		    value = *++argv;
		else {
		    fprintf(stderr, "%s: option `-%c' requires an argument\n",
			    pname, c);
		    return 1;
		}
		switch (c) {
		  case 'o': case 'O':
		    newoffset = parse_num(value, NULL);
		    break;
		  case 'w': case 'W':
		    newwidth = parse_num(value, NULL);
		    break;
		}
		break;
	      case 'f': case 'F':
		fix_mode = TRUE;
		break;
	      case 'l': case 'L':
		look_mode = TRUE;
		break;
	      case 'e': case 'E':
		eager_mode = TRUE;
		break;
	      case 'D':
		write_default_rc();
		return 0;
		break;
	    }
	} else {
	    if (filename) {
		fprintf(stderr, "%s: multiple filenames specified\n", pname);
		return 1;
	    }
	    filename = p;
	}
    }

    if (!filename) {
	fprintf(stderr, "%s: no filename specified\n", pname);
	return 1;
    }

    read_rc();
    if (newoffset != -1)
	realoffset = newoffset;
    if (newwidth != -1)
	width = newwidth;
    load_file (filename);
    init();
    fix_offset();
    do {
	draw_scr ();
	proc_key ();
    } while (!finished);
    done();

    return 0;
}

/*
 * Fix up `offset' to match `realoffset'. Also, while we're here,
 * enable or disable ASCII mode and sanity-check the width.
 */
void fix_offset(void) {
    if (3*width+11 > display_cols) {
	width = (display_cols-11) / 3;
	sprintf (message, "Width reduced to %"OFF"d to fit on the screen", width);
    }
    if (4*width+14 > display_cols) {
	ascii_enabled = FALSE;
	if (edit_type == 0)
	    edit_type = 1;	       /* force to hex mode */
    } else
	ascii_enabled = TRUE;
    offset = realoffset % width;
    if (!offset)
	offset = width;
}

/*
 * Initialise stuff at the beginning of the program: mostly the
 * display.
 */
static void init(void) {
    int i;

    display_setup();

    display_define_colour(COL_BUFFER, -1, -1, FALSE);
    display_define_colour(COL_SELECT, 0, 7, TRUE);
    display_define_colour(COL_STATUS, 11, 4, TRUE);
    display_define_colour(COL_ESCAPE, 9, 0, FALSE);
    display_define_colour(COL_INVALID, 11, 0, FALSE);

    for (i=0; i<256; i++) {
	sprintf(hex[i], "%02X", i);
	toprint[i] = (i>=32 && i<127 ? i : '.');
    }
}

/*
 * Clean up all the stuff that init() did.
 */
static void done(void) {
    display_cleanup();
}

/*
 * Load the file specified on the command line.
 */
static void load_file (char *fname) {
    FILE *fp;

    file_size = 0;
    if ( (fp = fopen (fname, "rb")) ) {
	if (eager_mode) {
	    size_t len;
	    static char buffer[4096];

	    filedata = buf_new_empty();

	    file_size = 0;

	    /*
	     * We've opened the file. Load it.
	     */
	    while ( (len = fread (buffer, 1, sizeof(buffer), fp)) > 0 ) {
		buf_insert_data (filedata, buffer, len, file_size);
		file_size += len;
	    }
	    fclose (fp);
	    assert(file_size == buf_length(filedata));
	    sprintf(message, "loaded %s (size %"OFF"d == 0x%"OFF"X).",
		    fname, file_size, file_size);
	} else {
	    filedata = buf_new_from_file(fp);
	    file_size = buf_length(filedata);
	    sprintf(message, "opened %s (size %"OFF"d == 0x%"OFF"X).",
		    fname, file_size, file_size);
	}
	new_file = FALSE;
    } else {
	if (look_mode || fix_mode) {
	    fprintf(stderr, "%s: file %s not found, and %s mode active\n",
		    pname, fname, (look_mode ? "LOOK" : "FIX"));
	    exit (1);
	}
	filedata = buf_new_empty();
	sprintf(message, "New file %s.", fname);
	new_file = TRUE;
    }
}

/*
 * Save the file. Return TRUE on success, FALSE on error.
 */
int save_file (void) {
    FILE *fp;
    fileoffset_t pos = 0;

    if (look_mode)
	return FALSE;		       /* do nothing! */

    if ( (fp = fopen (filename, "wb")) ) {
	static char buffer[SAVE_BLKSIZ];

	while (pos < file_size) {
	    fileoffset_t size = file_size - pos;
	    if (size > SAVE_BLKSIZ)
		size = SAVE_BLKSIZ;

	    buf_fetch_data (filedata, buffer, size, pos);
	    if (size != fwrite (buffer, 1, size, fp)) {
		fclose (fp);
		return FALSE;
	    }
	    pos += size;
	}
    } else
	return FALSE;
    fclose (fp);
    return TRUE;
}

/*
 * Make a backup of the file, if such has not already been done.
 * Return TRUE on success, FALSE on error.
 */
int backup_file (void) {
    char backup_name[FILENAME_MAX];

    if (new_file)
	return TRUE;		       /* unnecessary - pretend it's done */
    strcpy (backup_name, filename);
#if defined(unix) && !defined(GO32)
    strcat (backup_name, ".bak");
#elif defined(MSDOS)
    {
	char *p, *q;

	q = NULL;
	for (p = backup_name; *p; p++) {
	    if (*p == '\\')
		q = NULL;
	    else if (*p == '.')
		q = p;
	}
	if (!q)
	    q = p;
	strcpy (q, ".BAK");
    }
#endif
    remove (backup_name);	       /* don't care if this fails */
    return !rename (filename, backup_name);
}

static unsigned char *scrbuf = NULL;
static int scrbuflines = 0;

/*
 * Draw the screen, for normal usage.
 */
void draw_scr (void) {
    int scrsize, scroff, llen, i, j;
    fileoffset_t currpos;
    fileoffset_t marktop, markbot;
    int mark;
    char *p;
    unsigned char c, *q;
    char *linebuf;

    scrlines = display_rows - 2;
    if (scrlines > scrbuflines) {
	scrbuf = (scrbuf ?
		  realloc(scrbuf, scrlines*width) :
		  malloc(scrlines*width));
	if (!scrbuf) {
	    done();
	    fprintf(stderr, "%s: out of memory!\n", pname);
	    exit (2);
	}
	scrbuflines = scrlines;
    }

    linebuf = malloc(width*4+20);
    if (!linebuf) {
	done();
	fprintf(stderr, "%s: out of memory!\n", pname);
	exit (2);
    }
    memset (linebuf, ' ', width*4+13);
    linebuf[width*4+13] = '\0';

    if (top_pos == 0)
	scroff = width - offset;
    else
	scroff = 0;
    scrsize = scrlines * width - scroff;
    if (scrsize > file_size - top_pos)
	scrsize = file_size - top_pos;

    buf_fetch_data (filedata, scrbuf, scrsize, top_pos);

    scrsize += scroff;		       /* hack but it'll work */

    mark = marking && (cur_pos != mark_point);
    if (mark) {
	if (cur_pos > mark_point)
	    marktop = mark_point, markbot = cur_pos;
	else
	    marktop = cur_pos, markbot = mark_point;
    } else
	marktop = markbot = 0;	       /* placate gcc */

    currpos = top_pos;
    q = scrbuf;

    for (i=0; i<scrlines; i++) {
	display_moveto (i, 0);
	if (currpos<=cur_pos || currpos<file_size) {
	    p = hex[(currpos >> 24) & 0xFF];
	    linebuf[0]=p[0];
	    linebuf[1]=p[1];
	    p = hex[(currpos >> 16) & 0xFF];
	    linebuf[2]=p[0];
	    linebuf[3]=p[1];
	    p = hex[(currpos >> 8) & 0xFF];
	    linebuf[4]=p[0];
	    linebuf[5]=p[1];
	    p = hex[currpos & 0xFF];
	    linebuf[6]=p[0];
	    linebuf[7]=p[1];
	    for (j=0; j<width; j++) {
		if (scrsize > 0) {
		    if (currpos == 0 && j < width-offset)
			p = "  ", c = ' ';
		    else
			p = hex[*q], c = *q++;
		    scrsize--;
		} else {
		    p = "  ", c = ' ';
		}
		linebuf[11+3*j]=p[0];
		linebuf[12+3*j]=p[1];
		linebuf[13+3*width+j]=toprint[c];
	    }
	    llen = (currpos ? width : offset);
	    if (mark && currpos<markbot && currpos+llen>marktop) {
		/*
		 * Some of this line is marked. Maybe all. Whatever
		 * the precise details, there will be two regions
		 * requiring highlighting: a hex bit and an ascii
		 * bit.
		 */
		fileoffset_t localstart= (currpos<marktop ? marktop :
                                          currpos) - currpos;
		fileoffset_t localstop = (currpos+llen>markbot ? markbot :
                                          currpos+llen) - currpos;
		localstart += width-llen;
		localstop += width-llen;
		display_write_chars(linebuf, 11+3*localstart);
		display_set_colour(COL_SELECT);
		display_write_chars(linebuf+11+3*localstart,
				   3*(localstop-localstart)-1);
		display_set_colour(COL_BUFFER);
		if (ascii_enabled) {
		    display_write_chars(linebuf+10+3*localstop,
				       3+3*width+localstart-3*localstop);
		    display_set_colour(COL_SELECT);
		    display_write_chars(linebuf+13+3*width+localstart,
				       localstop-localstart);
		    display_set_colour(COL_BUFFER);
		    display_write_chars(linebuf+13+3*width+localstop,
				       width-localstop);
		} else {
		    display_write_chars(linebuf+10+3*localstop,
				       2+3*width-3*localstop);
		}
	    } else {
                display_set_colour(COL_BUFFER);
		display_write_chars(linebuf,
				   ascii_enabled ? 13+4*width : 10+3*width);
            }
	}
	currpos += (currpos ? width : offset);
	display_clear_to_eol();
    }

    {
	char status[80];
	int slen;
	display_moveto (display_rows-2, 0);
	display_set_colour(COL_STATUS);
	sprintf(status, statfmt,
		(modified ? "**" : "  "),
		filename,
		(insert_mode ? "(Insert)" :
		 look_mode ? "(LOOK)  " :
		 fix_mode ? "(FIX)   " : "(Ovrwrt)"),
		cur_pos, file_size);
	slen = strlen(status);
	if (slen > display_cols)
	    slen = display_cols;
	display_write_chars(status, slen);
	while (slen++ < display_cols)
	    display_write_str(" ");
	display_set_colour(COL_BUFFER);
    }

    display_moveto (display_rows-1, 0);
    display_write_str (message);
    display_clear_to_eol();
    message[0] = '\0';

    i = cur_pos - top_pos;
    if (top_pos == 0)
	i += width - offset;
    j = (edit_type ? (i%width)*3+10+edit_type : (i%width)+13+3*width);
    if (j >= display_cols)
	j = display_cols-1;
    free (linebuf);
    display_moveto (i/width, j);
    display_refresh ();
}

volatile int safe_update, update_required;
void update (void);

/*
 * Get a string, in the "minibuffer". Return TRUE on success, FALSE
 * on break. Possibly syntax-highlight the entered string for
 * backslash-escapes, depending on the "highlight" parameter.
 */
int get_str (char *prompt, char *buf, int highlight) {
    int maxlen = 79 - strlen(prompt);  /* limit to 80 - who cares? :) */
    int len = 0;
    int c;

    for (EVER) {
	display_moveto (display_rows-1, 0);
	display_set_colour (COL_MINIBUF);
	display_write_str (prompt);
	if (highlight) {
	    char *q, *p = buf, *r = buf+len;
	    while (p<r) {
		q = p;
		if (*p == '\\') {
		    p++;
		    if (p<r && *p == '\\')
			p++, display_set_colour(COL_ESCAPE);
		    else if (p>=r || !isxdigit ((unsigned char)*p))
			display_set_colour(COL_INVALID);
		    else if (p+1>=r || !isxdigit ((unsigned char)p[1]))
			p++, display_set_colour(COL_INVALID);
		    else
			p+=2, display_set_colour(COL_ESCAPE);
		} else {
		    while (p<r && *p != '\\')
			p++;
		    display_set_colour (COL_MINIBUF);
		}
		display_write_chars (q, p-q);
	    }
	} else
	    display_write_chars (buf, len);
	display_set_colour (COL_MINIBUF);
	display_clear_to_eol();
	display_refresh();
	if (update_required)
	    update();
	safe_update = TRUE;
	c = display_getkey();
	safe_update = FALSE;
	if (c == 13 || c == 10) {
	    buf[len] = '\0';
	    return TRUE;
	} else if (c == 27 || c == 7) {
	    display_beep();
	    display_post_error();
	    strcpy (message, "User Break!");
	    return FALSE;
	}

	if (c >= 32 && c <= 126) {
	    if (len < maxlen)
		buf[len++] = c;
	    else
		display_beep();
	}

	if ((c == 127 || c == 8) && len > 0)
	    len--;

	if (c == 'U'-'@')	       /* ^U kill line */
	    len = 0;
    }
}

/*
 * Take a buffer containing possible backslash-escapes, and return
 * a buffer containing a (binary!) string. Since the string is
 * binary, it cannot be null terminated: hence the length is
 * returned from the function. The string is processed in place.
 * 
 * Escapes are simple: a backslash followed by two hex digits
 * represents that character; a doubled backslash represents a
 * backslash itself; a backslash followed by anything else is
 * invalid. (-1 is returned if an invalid sequence is detected.)
 */
int parse_quoted (char *buffer) {
    char *p, *q;

    p = q = buffer;
    while (*p) {
	while (*p && *p != '\\')
	    *q++ = *p++;
	if (*p == '\\') {
	    p++;
	    if (*p == '\\')
		*q++ = *p++;
	    else if (p[1] && isxdigit((unsigned char)*p) &&
		     isxdigit((unsigned char)p[1])) {
		char buf[3];
		buf[0] = *p++;
		buf[1] = *p++;
		buf[2] = '\0';
		*q++ = strtol(buf, NULL, 16);
	    } else
		return -1;
	}
    }
    return q - buffer;
}

/*
 * Suspend program. (Or shell out, depending on OS, of course.)
 */
void suspend(void) {
#if defined(unix) && !defined(GO32)
    done();
    raise (SIGTSTP);
    init();
#elif defined(MSDOS)
    done();
    spawnl (P_WAIT, getenv("COMSPEC"), "", NULL);
    init();
#else
    display_beep();
    strcpy(message, "Suspend function not yet implemented.");
#endif
}

void update (void) {
    display_recheck_size();
    fix_offset ();
    draw_scr ();
}

void schedule_update(void) {
    if (safe_update)
	update();
    else
	update_required = TRUE;
}

fileoffset_t parse_num (char *buffer, int *error) {
    if (error)
	*error = FALSE;
    if (!buffer[strspn(buffer, "0123456789")]) {
	/* interpret as decimal */
	return ATOOFF(buffer);
    } else if (buffer[0]=='0' && (buffer[1]=='X' || buffer[1]=='x') &&
	       !buffer[2+strspn(buffer+2,"0123456789ABCDEFabcdef")]) {
	return STRTOOFF(buffer+2, NULL, 16);
    } else if (buffer[0]=='$' &&
	       !buffer[1+strspn(buffer+1,"0123456789ABCDEFabcdef")]) {
	return STRTOOFF(buffer+1, NULL, 16);
    } else {
	return 0;
	if (error)
	    *error = TRUE;
    }
}
