/* fv is a binary file editor written by Chris Hooper (cdh@mtu.edu)
 *	on 6-2-91 and has gone through many revisions since then.
 *      Much inspiration was given by Bill Moore, the first real user!
 *
 *   Author:  CHRISTOPHER D HOOPER
 *
 *   fv source Copyright (c) 1992 - 1998  Chris Hooper
 *
 *   Modification and redistribution is strictly prohibited.
 *   Sale of this software above media cost is prohibited.
 *
 *   Except for above two restrictions, this software may be used for
 *       any purpose, commercial or private.
 *
 *   Disclaimer:  This product is fit for no use, foreign or domestic.
 *                Use implies knowledge you intend to destroy something.
 */

#include <stdio.h>
#include <curses.h>
#include "main.h"
#if !defined(Amiga) && !defined(OS9) && !defined(FreeBSD)
#	include <malloc.h>
#endif
#if defined(Win32) || defined(Solaris1)
char *strdup();
#else
#include <strings.h>
#endif

#include "screen.h"
#include "io.h"

#ifdef LINT
#	include "lint.h"
#endif

#ifdef OS9
char *strdup();
#endif

extern ulong cursor_address;
extern ulong inbuf_size;
extern int status_line;
extern char *NONWORD;

#define MARKS   64

char	markchar[MARKS];
int	markpos[MARKS];
int	marks = 0;

char *ubar = "_____________________________________________________________________________________________________________________________________________________________________________________________________";


void delchars();
void delword();
void help_me_prompt();
static char *xbytes();
#ifdef COLON_MODE
static int know_file();
#endif

int save_changes(prompt_user)
int prompt_user;
{
	char ch;
	int saveprompt = 0;
	int yes = 1;

	if (!dirty)
	    return(0);

#if !defined(Dynix) && !defined(Linux) && !defined(FreeBSD) && !defined(HPUX)
	if (!noscreen)
	    raw();
#endif

	if ((ronly | force_ronly) && !force_write) {
	    xmove(status_window, 0, COLS - 44);
	    xaddstr(status_window,
	            "  File is read only, discard changes? (Y/n)");
	} else {
	    saveprompt = 1;
	    xmove(status_window, 0, COLS - 22);
	    xaddstr(status_window, "  Save changes? (Y/n)");
	}
	xclrtoeol(status_window);
	xrefresh(status_window);

	while (prompt_user) {
	    ch = get_key();
	    switch (ch) {
		case 27:	/* ESC */
		case 3: 	/* ^C */
		case 24:	/* ^X */
		    goto no_save;
		case 'y':	/* Yes */
		case 'Y':
		case 13:	/* ^M */
		case 10:	/* ^J */
		    prompt_user = 0;
		    break;
		case 'n':	/* No */
		case 'N':
		    yes = 0;
		    prompt_user = 0;
		    break;
	    }
	}

	if (yes && saveprompt)
		buf_write(daddr, buffer, inbuf_size);

	if (yes || saveprompt)
	    dirty = 0;

	no_save:
	xmove(status_window, 0, COLS - 44);
	xclrtoeol(status_window);
	xrefresh(status_window);
	status_mode();

#if !defined(Dynix) && !defined(Linux) && !defined(FreeBSD) && !defined(HPUX)
	if (!noscreen) {
		noraw();
#	if defined(Solaris2)
		cbreak();
#	endif
	}
#endif
	return(dirty);
}

int confirm(message)
char *message;
{
	char ch = ' ';

	xmove(status_window, 0, 20);
	xclrtoeol(status_window);
	xmove(status_window, 0, COLS - strlen(message) - 1);
	xaddstr(status_window, message);
	xrefresh(status_window);
	while ((ch != 'y') && (ch != 'n')) {
		ch = get_key();
		if ((ch == 'Y') || (ch == '\015') || (ch == '\012'))
			ch = 'y';
		if (ch == 'N')
			ch = 'n';
	}
	xmove(status_window, 0, COLS - 20);
	xclrtoeol(status_window);
	xrefresh(status_window);
	if (ch == 'y')
		return(1);
	else
		return(0);
}

int get_jump(start)
int start;
{
	char	ch = ' ';
	char	str[12];
	int	index;
	static	int hexdec = -1;
	ulong	retval;
	WINDOW	*jump_window;

	if (hexdec == -1)
	    hexdec = wdismode;

	jump_window = xnewwin(1, 34, status_line, COLS - 34);
	xmove(jump_window, 0, 0);
	xclrtoeol(jump_window);
	xaddstr(jump_window, "Go to address:");

	xrefresh(jump_window);

	str[0] = '\0';

	index = 0;
	while (ch != 13) {	/* ^M */
	    xmove(jump_window, 0, 15);
	    xclrtoeol(jump_window);
	    xmove(jump_window, 0, 30);
	    if (hexdec)
		xaddstr(jump_window, "Dec");
	    else
		xaddstr(jump_window, "Hex");
	    xmove(jump_window, 0, 15);
	    xaddstr(jump_window, str);
	    xrefresh(jump_window);
	    ch = get_key();
	    switch (ch) {
		case 8:		/* ^H - delete */
		case 127:	/* ^? */
		    if (index > 0) {
			index--;
			str[index] = '\0';
		    }
		    break;
		case 21:	/* ^U */
		    index = 0;
		    str[index] = '\0';
		    break;
		case 'g':
		    ch = 13;
		    break;
		case 9:		/* TAB */
		case 't':
		case 'T':
		    hexdec = !hexdec;

		    if (index != 0) {
			if (hexdec) {
			    sscanf(str, "%x", (uint *) &retval);
			    sprintf(str, "%d", (uint) retval);
			} else {
			    sscanf(str, "%d", (uint *) &retval);
			    sprintf(str, "%x", (uint) retval);
			}
		        index = strlen(str);
		    }
		    break;
		case 'q':	/* ^X */
		case 24:	/* ^X */
		case 27:	/* ESC */
		    index = 0;
		    ch = 13;
		    break;
		default:
		    if ((ch >= 'A') && (ch <= 'F'))
			ch += 'a' - 'A';

		    if (hexdec) {	/* DEC */
			if ((index < 10) && (ch >= '0') && (ch <= '9')) {
			    str[index] = ch;
			    index++;
			    str[index] = '\0';
			}
		    } else		/* HEX */
			if ((index < 8) &&
		            (((ch >= '0') && (ch <= '9')) ||
		             ((ch >= 'a') && (ch <= 'f')))) {
			    str[index] = ch;
			    index++;
			    str[index] = '\0';
			}
	    }

	}
	retval = start;

	if (index == 0)
		return(-1);

	if (hexdec)
	    sscanf(str, "%d", (uint *) &retval);
	else
	    sscanf(str, "%x", (uint *) &retval);

	if (retval == fend)
	    retval = fend - 1;

	xmove(jump_window, 0, 0);
	xclrtoeol(jump_window);
	xrefresh(jump_window);

	xdelwin(jump_window);
	return(retval);
}

int set_mark(addr)
int addr;
{
	char ch;
	int index;

	ch = get_key();
	for (index = 0; index < MARKS; index++)
	    if (markchar[index] == ch) {
		markpos[index] = addr;
		return(0);
	    }
	markchar[marks]	= ch;
	markpos[marks]	= addr;
	marks = (marks + 1) % MARKS;
	return(0);
}

int get_mark(start)
int start;
{
	int	ch;
	int	index;

	ch = get_key();
	if ((ch == '\'') || (ch == '`'))
	    return(last_viewed);
	for (index = 0; index < MARKS; index++)
	    if (markchar[index] == ch)
		return(markpos[index]);
	return(start);
}


int prompt_string(string, title)
char *string;
char *title;
{
	char	ch	= ' ';
	char	ch2;
	WINDOW	*jump_window;
	char	*prevstr;
	char	*origstr;
	char	*temp;
	int	index	= 0;
	int	index2;
	int	slen;
	int	origlen;
#ifdef STRICT_VI
	int	emode = -1;
#else
	int	emode = 0;
#endif
	int	rc = 0;
	int	titlelen = 0;

	/* must take into account that string length can be greater
	   then the maximum displayable area on the line. */

	/* original copy of string */
	origstr	= (char *) malloc(EDIT_STRLEN);
	if (origstr == NULL)
		error_exit("edit_prompt_string: malloc failed");
	strcpy(origstr, string);

	/* edit updated copy of string */
	prevstr	= (char *) malloc(EDIT_STRLEN);
	if (prevstr == NULL)
		error_exit("edit_prompt_string: malloc failed");
	strcpy(prevstr, string);

	origlen	 = strlen(string);
	titlelen = strlen(title);
	index	 = origlen - 1;

	jump_window = xnewwin(1, COLS - 31, status_line, 20);
	xmove(jump_window, 0, 0);
	xaddstr(jump_window, title);

	while ((ch != 13) && (ch != 4)) {	/* ^M or ^D */
	    slen = strlen(string);
	    xstandout(jump_window);
	    xmove(jump_window, 0, titlelen);
	    if (slen <= origlen) {
	        sprintf(out_string, "%s", string);
	        xaddstr(jump_window, out_string);
	        sprintf(out_string, "%.*s", origlen -
			(int) strlen(out_string), ubar);
	        xaddstr(jump_window, out_string);
	        xstandend(jump_window);
	        sprintf(out_string, "%.*s", EDIT_STRLEN - origlen, ubar);
	        xaddstr(jump_window, out_string);
	    } else {
	        sprintf(out_string, "%.*s", origlen, string);
	        xaddstr(jump_window, out_string);
	        xstandend(jump_window);
	        sprintf(out_string, "%s", string + origlen);
	        xaddstr(jump_window, out_string);
	        sprintf(out_string, "%.*s", EDIT_STRLEN - slen, ubar);
	        xaddstr(jump_window, out_string);
	    }
	    xmove(jump_window, 0, index + titlelen);
	    xrefresh(jump_window);

	    ch = get_key();

	    switch (ch) {
	        case 2: 	/* ^B */
	            index = 0;
	            break;
	        case 5: 	/* ^E */
	            index = slen + emode;
	            break;
	        case 8: 	/* ^H - delete */
	        case 127:	/* ^? */
#ifdef STRICT_VI
	            if (emode) {
	                if (index > 0)
	                    index--;
	                break;
	            }
#endif
	            strcpy(prevstr, string);
	            if (index > 0) {
	                index--;
	                for (index2 = index; index2 < slen; index2++)
	                        string[index2] = string[index2 + 1];
	            }
	            break;
	        case '':
	            if (index > 0)
	                index--;
	            break;
	        case 12: 	/* ^L */
	            if (index < slen)
	                index++;
	            break;
	        case 10:	/* ^J */
		    ch = 13;
	        case 13:	/* ^M */
	        case 4: 	/* ^D */
	            break;
	        case 21:	/* ^U */
	            strcpy(prevstr, string);
	            index = 0;
		    string[index] = '\0';
		    xmove(jump_window, 0, titlelen);
		    xclrtoeol(jump_window);
		    break;
		case 24:	/* ^X */
		case 3:		/* ^C */
		    goto quit;
		    break;
		case 27:	/* ESC */
		    emode = -1;
		    if (index > slen - 1)
			index--;
		    break;
		default:
		    if (emode) {
			switch(ch) {
			    case 'a':
				strcpy(prevstr, string);
				emode = 0;
				if (index < slen)
				    index++;
				break;
			    case 'A':
				strcpy(prevstr, string);
				emode = 0;
				index = slen;
				break;
			    case 'c':
				emode = 0;
			    case 'd':
				strcpy(prevstr, string);
				ch2 = get_key();
				if (ch2 == 'w')
				    delword(string + index, slen);
				else if (ch2 == 't') {
				    ch2 = get_key();
				    for (index2 = index + 1; index2 < slen;
					index2++)
					if (string[index2] == ch2) {
					    delchars(string + index, index2 -
						     index, slen);
					    break;
					}
				}
				if (index > ((int) strlen(string) - 1))
					index = strlen(string) - 1;
				break;
			    case 'H':
			    case '?':
				help_me(3);
				xtouchwin(jump_window);
				xtouchwin(main_window);
				xrefresh(jump_window);
				xrefresh(main_window);
				break;
			    case 'h':
			    case 'k':
			    case 2:	/* ^B */
				if (index > 0)
				    index--;
				break;
			    case 'I':
				index = 0;
			    case 'i':
				strcpy(prevstr, string);
				emode = 0;
				break;
			    case 'j':
			    case 'l':
			    case ' ':
			    case 6:	/* ^F */
				if (index < slen - 1)
				    index++;
				break;
			    case 'r':
			    case 's':
				strcpy(prevstr, string);
				string[index] = get_key();
				break;
			    case 't':
			    case 'T':
				ch2 = get_key();
				for (index2 = index + 1; index2 < slen;index2++)
					if (string[index2] == ch2) {
						index = index2 - 1;
						break;
					}
				break;
			    case 'u':
				temp	= prevstr;
				prevstr	= string;
				string	= temp;
				break;
			    case 'U':
				temp	= prevstr;
				prevstr	= string;
				string	= temp;
				strcpy(string, origstr);
				break;
			    case 'x':
				strcpy(prevstr, string);
				for (index2 = index; index2 < slen;index2++)
					string[index2] = string[index2 + 1];
				if (index == slen - 1)
					index--;
				break;
			    case '0':
			    case '^':
				index = 0;
				break;
			    case '$':
				index = slen;
				break;
			}
			break;
		    }
		    if (ch == 22)	/* ^V */
			ch = get_key();
		    if (slen == EDIT_STRLEN)
			break;
		    for (index2 = slen; index2 > index; index2--)
			string[index2] = string[index2 - 1];
		    string[slen + 1] = '\0';
		    string[index++] = ch;
		    ch = ' ';
		    break;
	    }
	}

	if ((ch == '\015') || (ch == '\012'))
		rc = 2;
	else
		rc = 1;

	quit:

	xmove(jump_window, 0, 0);
	xclrtoeol(jump_window);
	xrefresh(jump_window);

	xdelwin(jump_window);

	return(rc);
}

void delword(string, slen)
char	*string;
int	slen;
{
	int index;

	for (index = 1; index < slen; index++)
	    if (strchr(NONWORD, string[index]))
		break;
	delchars(string, index, slen);
}

void delchars(string, index, slen)
char	*string;
int	index;
int	slen;
{
	int index2;

	for (index2 = index; index2 <= slen; index2++)
	    string[index2 - index] = string[index2];
}

int write_file(first, last, type)
int first;
int last;
int type;
{
	char	name[128];
	char	tmp[128];
	char	*buf;
	ulong	size;
	FILE	*out_file;

	size = last - first;
	if (size > 65536) {
	    if (wdismode)
		sprintf(tmp, "Write %d-%d, %s?  (Y/n)",
			first, last, xbytes(size));
	    else
		sprintf(tmp, "Write %08x-%08x, %s?  (Y/n)",
			first, last, xbytes(size));
	    if (!confirm(tmp))
		return(0);
	}
	
	top:
	strcpy(name, "output");

	buf = (char *) malloc(size);
	if (buf == NULL)
	    error_exit("write_file: malloc failed");
	if (prompt_string(name, "Filename: ")) {
	    if (type) {
#ifdef Win32
		out_file = fopen(name, "ab+");
		if (out_file == NULL)
		    out_file = fopen(name, "wb");
	    } else
		out_file = fopen(name, "wb");
#else
		out_file = fopen(name, "a+");
		if (out_file == NULL)
		    out_file = fopen(name, "w");
	    } else
		out_file = fopen(name, "w");
#endif
	    if (out_file == NULL) {
		sprintf(tmp, "Unable to open %s for writing.  Another?", name);
		if (confirm(tmp))
		    goto top;
		else {
		    xmove(status_window, 0, 20);
		    xclrtoeol(status_window);
		    xrefresh(status_window);
		    return(0);
		}
	    }
	    if (buf_read(first, buf, size) == 0)
		fwrite(buf, size, 1, out_file);
	    fclose(out_file);
	}
	xmove(status_window, 0, 20);
	if (wdismode)
	    sprintf(tmp, "Wrote %d-%d, %s.", first, last, xbytes(size));
	else
	    sprintf(tmp, "Wrote %08x-%08x, %s.", first, last, xbytes(size));
	xaddstr(status_window, tmp);

	xclrtoeol(status_window);
	xrefresh(status_window);
	return(1);
}

int write_mark(start, type)
int start;
int type;
{
	int	ch;
	int	temp;
	int	end = 0;

	if (MARKS == 0) {
	    xmove(status_window, 0, 20);
	    xaddstr(status_window, "No marks set, press return.");
	    goto mark_error;
	}
	leaveok(status_window, FALSE);
	xmove(status_window, 0, 20);
	xaddstr(status_window, "pick end mark ");
	xclrtoeol(status_window);
	xrefresh(status_window);

	if ((ch = get_key()) == '\'') {
	    xmove(status_window, 0, 20);
	    xaddstr(status_window, "pick end mark \'");
	    xrefresh(status_window);
	    ch = get_key();
	}
	leaveok(status_window, TRUE);

	switch (ch) {
		case 27:	/* ESC */
		case 10:	/* LF */
		case 13:	/* CR */
			xmove(status_window, 0, 20);
			xclrtoeol(status_window);
			xrefresh(status_window);
			return(1);	
	}

	for (temp = 0; temp < MARKS; temp++)
		if (markchar[temp] == ch) {
			end = markpos[temp];
			break;
		}

	if (temp == MARKS) {
	    xmove(status_window, 0, 20);
	    xaddstr(status_window, "Mark ");
		if (ch < ' ') {
			xaddch(status_window, '^');
			xaddch(status_window, ch + '@');
		} else
			xaddch(status_window, ch);
			xaddstr(status_window, " unknown, press return.");
		goto mark_error;
	}
	
	if (start > end) {
	    write_file(end, start, type);
	    return(0);
	} else if (start < end) {
	    write_file(start, end, type);
	    return(0);
	}

	xmove(status_window, 0, 20);
	xaddstr(status_window, "Can't write 0 bytes, press return.");

	mark_error:
	xclrtoeol(status_window);
	xrefresh(status_window);
	while ((ch = get_key()) != EOF)
		if ((ch == 13) || (ch == 10))	/* ^M or ^J */
			break;
	xmove(status_window, 0, 20);
	xclrtoeol(status_window);
	xrefresh(status_window);
	return(1);
}

static char *xbytes_val[] = {
	"", "K", "M", "G", "T"
};

static char *xbytes(value)
ulong value;
{
	static char buf[32];
	int mul = 0;
	while ((value > 65535) || ((mul > 1) && (value > 8192))) {
	    value >>= 10;
	    mul++;
	}
	sprintf(buf, "%d %sbytes", (uint) value, xbytes_val[mul]);

	return(buf);
}

#ifdef COLON_MODE
int colon_mode()
{
	char ch;
	char buf[128];
	int index = 0;
	int rc = 0;
	int temp;
	char *ptr;
	char *ptr2;

	leaveok(status_window, FALSE);
	xmove(status_window, 0, 0);
	xaddch(status_window, ':');
	xclrtoeol(status_window);
	xrefresh(status_window);

	buf[0] = '\0';
	do {
	    xmove(status_window, 0, 1);
	    xaddstr(status_window, buf);
	    xrefresh(status_window);
	    ch = get_key();
	    switch (ch) {
		case 13:	/* ^M */
		case 10:	/* ^J */
		    ch = 13;
		    break;
		case 8:		/* ^H */
		case 127:	/* ^? */
		    if (index > 0) {
			xmove(status_window, 0, index);
			xclrtoeol(status_window);
			buf[--index] = '\0';
		    } else
			ch = 13;
		    break;
		default:
		    buf[index++] = ch;
		    buf[index] = '\0';
	    }
	} while (ch != 13); 	/* ^M */

	leaveok(status_window, TRUE);
	xmove(status_window, 0, 0);
	xclrtoeol(status_window);
	xrefresh(status_window);

	switch (buf[0]) {
	    case '\0':
		break;
	    case 'h':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'e') || (buf[2] != 'l') || (buf[3] != 'p')))
		    goto bad_command;
	    case '?':
		help_me(5);
		xtouchwin(main_window);
		xrefresh(main_window);
		break;
	    case 'e':
		if ((buf[1] != '\0') && (buf[1] != '!') && (buf[1] != ' ') &&
		    ((buf[1] != 'd') || (buf[2] != 'i') || (buf[3] != 't')))
		    goto bad_command;

		for (ptr = buf; *ptr != '\0'; ptr++)
		    if (*ptr == ' ') {
			ptr++;
			break;
		    }

		index = filenum;
		for (ptr2 = ptr; *ptr != '\0'; ptr++)
		    if ((*ptr == ' ') || (*ptr == '\t')) {
			*ptr = '\0';
			if (index == filenum)
			    index = know_file(ptr2);
			else
			    know_file(ptr2);
			ptr2 = ptr + 1;
		    }

		if (*ptr2 != '\0')
		    if (index == filenum)
			index = know_file(ptr2);
		    else
			know_file(ptr2);

		temp = index;
		if ((buf[1] == '!') || (buf[4] == '!'))
		    dirty = 0;

		goto new_file;
		
		break;
	    case 'n':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'e') || (buf[2] != 'x') || (buf[3] != 't')))
		    goto bad_command;

		temp = filenum + 1;
		if (temp == filenames) {
		    xmove(status_window, 0, 20);
		    xaddstr(status_window, "No more files to edit");
		    xrefresh(status_window);
		    break;
		}

		new_file:
		if (save_changes(1))
		    break;

		close_file();
		view_address = 0;
		cursor_address = 0;
		daddr = -1;
		xmove(status_window, 0, 0);
		xclrtoeol(status_window);
		if (open_file(temp))
		    filenum = temp;
		else if (open_file(filenum) == 0) {
		    sprintf(out_string, "cannot reopen %s!\n",
		            filename[filenum]);
		    error_exit(out_string);
		}
		rc = 1;
		break;
	    case 'p':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'r') || (buf[2] != 'e')))
		    goto bad_command;

		temp = filenum - 1;
		if (temp == -1) {
		    xmove(status_window, 0, 20);
		    xaddstr(status_window, "No previous file to edit");
		    xrefresh(status_window);
		    break;
		}
		goto new_file;
	    case 'q':
		if ((buf[1] != '\0') && (buf[1] != '!') &&
		    ((buf[1] != 'u') || (buf[2] != 'i') || (buf[3] != 't')))
		    goto bad_command;

		/* if not q! then check if dirty, then if more files */
		if ((buf[1] != '!') && (buf[4] != '!')) {
		    if (dirty) {
			xmove(status_window, 0, 20);
			xaddstr(status_window, "No write since last change");
			xrefresh(status_window);
			break;
		    }
		    if (filenum != filenames - 1) {
			char buf[32];
			if (filenum == filenames - 2)
			    sprintf(buf, "There is one more file to edit.");
			else
			    sprintf(buf, "There are %d more files to edit.",
			            filenames - filenum - 1);
			xmove(status_window, 0, 20);
			xaddstr(status_window, buf);
			xrefresh(status_window);
			break;
		    }
		}
		dirty = 0;
		rc = -1;
		break;
	    case 'r':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'e') || (buf[2] != 'w')))
		    goto bad_command;

		temp = 0;
		goto new_file;
	    case 'w':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'r') || (buf[2] != 'i') ||
		     (buf[3] != 't') || (buf[4] != 'e')) &&
                    ((buf[1] != 'q') || (buf[2] != '\0')))
		    goto bad_command;

		if (buf[1] == 'q') {
		    save_changes(0);
		    rc = -1;
		    break;
		}
		dirty = 1;
		save_changes(0);
		xmove(status_window, 0, 20);
		xaddstr(status_window, "Updated ");
		xaddstr(status_window, filename[filenum]);
		xrefresh(status_window);
		break;
	    case 'x':
		if ((buf[1] != '\0') &&
		    ((buf[1] != 'i') || (buf[2] != 't')))
		    goto bad_command;

		if (save_changes(0))
		    rc = 0;
		else
		    rc = -1;
		break;
	    default:
		bad_command:
		xmove(status_window, 0, 20);
		xaddstr(status_window, "Unknown command ");
		xaddstr(status_window, buf);
		xrefresh(status_window);
		break;
	}

	return(rc);
}

static int know_file(name)
char *name;
{
    int index;

    for (index = 0; index < filenames; index++)
	if (!strcmp(filename[index], name))
	    return(index);

    filename[filenames] = strdup(name);

    return(filenames++);
}
#endif /* COLON_MODE */
