/*======================================================================*\
|*		Editor mined						*|
|*		auxiliary functions					*|
\*======================================================================*/

#include "mined.h"
#include "io.h"
#include <signal.h>

int panic_level = 0;		/* To adjust error handling to situation */


/*======================================================================*\
|*			Auxiliary routines				*|
\*======================================================================*/

/**
   Return the value of an environment variable.
   Do not return NULL.
 */
char *
envvar (name)
  char * name;
{
  char * val = getenv (name);
  if (val) {
	return val;
  } else {
	return "";
  }
}

/*
 * Delete file.
 */
void
delete_file (file)
  char * file;
{
#ifdef unix
  unlink (file);
#endif
#ifdef msdos
  unlink (file);
#endif
#ifdef vms
  delete (file);
#endif
}

/*
 * Panic () is called with a mined error msg and an optional system error msg.
 * It is called when something unrecoverable has happened.
 * It writes the message to the terminal, resets the tty and exits.
 * Ask the user if he wants to save his file.
 */
static
void
panic_msg (msg)
  char * msg;
{
  if (isscreenmode) {
	/* don't use status_msg here, msg may contain filename characters */
	status_line (msg, NIL_PTR);
	sleep (2);
  } else {
	(void) printf ("%s\n", msg);
  }
}

static
void
panicking (message, err, signum)
  register char * message;
  register char * err;
  register int signum;
{
  int panic_written;

  panic_level ++;
/*printf ("panicking, panic_level -> %d\n", panic_level);*/

  if (panic_level <= 2) {
	if (loading == False && modified) {
		panic_written = panicwrite ();
		if (panic_written == ERRORS) {
			sleep (2);
			build_string (text_buffer, "Error writing panic file %s", panic_file);
		} else {
			build_string (text_buffer, "Panic file %s written", panic_file);
		}
		ring_bell ();
		panic_msg (text_buffer);
	}
	if (signum != 0) {
		build_string (text_buffer, message, signum);
	} else if (err == NIL_PTR) {
		build_string (text_buffer, "%s", message);
	} else {
		build_string (text_buffer, "%s (%s)", message, err);
	}
	panic_msg (text_buffer);

	/* "normal" panic handling: */
	if (loading == False) {
		QUED ();	/* Try to save the file and quit */
		/* QUED returned: something wrong */
		sleep (2);
		panic_msg ("Aborted writing file in panic mode - trying to continue");
		panic_level --;
		return;
	}
  }

  if (panic_level <= 3) {
	if (isscreenmode) {
		set_cursor (0, YMAX);
		putchar ('\n');
#ifdef unix
		clear_window_title ();
#endif
		raw_mode (False);
	}
	delete_yank_files ();
  }
  exit (1) /* abort () sends IOT which would again be caught */;
}

void
panic (message, err)
  register char * message;
  register char * err;
{
  panicking (message, err, 0);
}

void
panicio (message, err)
  register char * message;
  register char * err;
{
/* Should panic_level already be increased here ? */
  panic (message, err);
}

void
catch_interrupt (signum)
  int signum;
{
  catch_signals ((signalfunc) catch_interrupt);
  if (signum == SIGTERM && panic_level == 0) {
	panic_level ++;
/*printf ("catch_interrupt -> EXMINED\n");*/
	EXMINED ();
	panic_level --;
  } else {
/*printf ("catch_interrupt -> panicking\n");*/
	panicking ("External signal %d caught - terminating", NIL_PTR, signum);
  }
}

/*-------------------------------------------------------------------------*/

/*
 * Memory allocation
 */
#ifdef msdos
# ifdef __TURBOC__
#include <alloc.h>
#define allocate farmalloc
#define freemem farfree
# else
extern void * malloc ();
extern void free ();
#define allocate malloc
#define freemem free
# endif
#else
extern void * malloc ();
extern void free ();
#define allocate malloc
#define freemem free
#endif

#define dont_debug_out_of_memory
#ifdef debug_out_of_memory
static int allocount = 0;
#endif

void *
alloc (bytes)
  int bytes;
{
#ifdef debug_out_of_memory
  allocount ++;
  if (allocount == 500) {
	return NIL_PTR;
  }
#endif
  return allocate ((unsigned) bytes);
/*
  char * p;

  if ((p = allocate ((unsigned) bytes)) == NIL_PTR) {
	panic ("Out of memory", NIL_PTR);
  }
  return p;
*/
}

void
free_space (p)
  void * p;
{
  freemem (p);
}

/*
 * free header list
 */

#define pointersize	sizeof (void *)
#define blocksizeof(typ) ((sizeof (typ) + pointersize - 1) / pointersize * pointersize)

LINE * free_header_list = NIL_LINE;

static
void
alloc_headerblock (n)
  int n;
{
  LINE * new_header;
  LINE * new_list;
  int i = 0;

  new_list = alloc (n * blocksizeof (LINE));
  if (new_list == NIL_LINE) {
	free_header_list = NIL_LINE;
  } else {
	while (i < n) {
		new_header = (LINE *) ((long) new_list + i * blocksizeof (LINE));
		new_header->next = free_header_list;
		free_header_list = new_header;
		i ++;
	}
  }
}

LINE *
alloc_header ()
{
/*  return alloc (sizeof (LINE)); */
  LINE * new_header;

  if (free_header_list == NIL_LINE) {
	alloc_headerblock (64);
	if (free_header_list == NIL_LINE) {
		alloc_headerblock (16);
		if (free_header_list == NIL_LINE) {
			alloc_headerblock (4);
			if (free_header_list == NIL_LINE) {
				alloc_headerblock (1);
				if (free_header_list == NIL_LINE) {
					return NIL_LINE;
				}
			}
		}
	}
  }
  new_header = free_header_list;
  free_header_list = free_header_list->next;
  return new_header;
}

void
free_header (hp)
  LINE * hp;
{
/*  freemem (hp); */
  hp->next = free_header_list;
  free_header_list = hp;
}

/*
 * Unnull () changes a NULL string pointer into an empty string pointer 
 * to allow easy feading of string results into build_string / sprintf
 */
char *
unnull (s)
  char * s;
{
  if (s == NIL_PTR) {
	return "";
  } else {
	return s;
  }
}

/*
 * Output an (unsigned) long in a 10 digit field without leading zeros.
 * It returns a pointer to the first digit in the buffer.
 */
static
char *
num_out (number, radix)
  long number;
  long radix;
{
  static char num_buf [11];		/* Buffer to build number */
  register long digit;			/* Next digit of number */
  register long pow;			/* Highest radix power of long */
  FLAG digit_seen = False;
  int i;
  int maxdigs;
  char * bufp = num_buf;

  if (radix == 16) {
	pow = 268435456L;
	maxdigs = 8;
  } else {
	pow = 1000000000L;
	maxdigs = 10;
	if (number < 0) {
		* bufp ++ = '-';
		number = - number;
	}
  }

  for (i = 0; i < maxdigs; i ++) {
	digit = number / pow;		/* Get next digit */
	if (digit == 0L && digit_seen == False && i != 9) {
	} else {
		if (digit > 9) {
			* bufp ++ = 'A' + (char) (digit - 10);
		} else {
			* bufp ++ = '0' + (char) digit;
		}
		number -= digit * pow;	/* Erase digit */
		digit_seen = True;
	}
	pow /= radix;			/* Get next digit */
  }
  * bufp = '\0';
  return num_buf;
}

char *
dec_out (number)
  long number;
{
  return num_out (number, 10);
}

/*
 * Build_string () prints the arguments as described in fmt, into the buffer.
 * %s indicates a string argument, %d indicates an integer argument.
 */
#ifndef build_string /* otherwise build_string is sprintf */

static
char *
hex_out (number)
  long number;
{
  return num_out (number, 16);
}

/* VARARGS */
void
build_string (buf, fmt, args)
  register char * buf;
  register char * fmt;
  int args;
{
  int * argptr = & args;
  char * scanp;
  FLAG islong;
  int length = 0;

  while (* fmt) {
     if (* fmt == '%') {
	fmt ++;
	fmt = scan_int (fmt, & length);
	if (* fmt == 'l') {
		islong = True;
		fmt ++;
	} else {
		islong = False;
	}
	switch (* fmt ++) {
	case 's' :
		scanp = (char *) * argptr;
		break;
	case 'd' :
		if (islong) {
			scanp = dec_out ((long) * ((long *) argptr));
			if (sizeof (long) > sizeof (int)) {
				argptr ++;
			}
		} else {
			scanp = dec_out ((long) * argptr);
		}
		break;
	case 'D' :
		scanp = dec_out ((long) * ((long *) argptr));
		if (sizeof (long) > sizeof (int)) {
			argptr ++;
		}
		break;
	case 'x' :
	case 'X' :
		scanp = hex_out ((long) * ((long *) argptr));
		if (sizeof (long) > sizeof (int)) {
			argptr ++;
		}
		break;
	default :
		scanp = "";
	}
	while ((* buf ++ = * scanp ++))
		{}
	buf --;
	argptr ++;
     } else {
	* buf ++ = * fmt ++;
     }
  }
  * buf = '\0';
}

#endif /* ndef build_string */

/*
 * scan_int () converts a string into a natural number
 * returns the character pointer behind the last digit
 */
char *
scan_int (str, nump)
  char * str;
  int * nump;
{
  register char * chpoi = str;
  int negative = False;

  while (* chpoi == ' ') {
	chpoi ++;
  }
  if (* chpoi == '-') {
	negative = True;
	chpoi ++;
  }
  if (* chpoi >= '0' && * chpoi <= '9') {
	* nump = 0;
  } else {
	return str;
  }
  while (* chpoi >= '0' && * chpoi <= '9' && quit == False) {
	* nump *= 10;
	* nump += * chpoi - '0';
	chpoi ++;
  }
  if (negative) {
	* nump = - * nump;
  }
  return chpoi;
}

/*
 * length_of () returns the number of bytes in the string 'string'
 * excluding the '\0'.
 */
int
length_of (string)
  register char * string;
{
  register int count = 0;

  if (string != NIL_PTR) {
	while (* string ++ != '\0') {
		count ++;
	}
  }
  return count;
}

/*
 * text_bytes_of () returns the number of bytes in the string 'string'
 * up to and excluding the first '\n'.
 */
static
int
text_bytes_of (string)
  register char * string;
{
  register int count = 0;

  if (string != NIL_PTR) {
	while (* string != '\0' && * string != '\n') {
		string ++;
		count ++;
	}
  }
  return count;
}

/*
 * copy_string () copies the string 'from' into the string 'to' which 
   must be long enough
 */
void
copy_string (to, from)
  register char * to;
  register char * from;
{
  while ((* to ++ = * from ++) != '\0')
	{}
}


/*======================================================================*\
|*			System error retrieval				*|
\*======================================================================*/

#ifdef __GNUC__

#include <errno.h>

#else	/* ifdef __GNUC__ */

#ifdef vms

/* #define includeerrno */
#  ifdef includeerrno
#  include <errno.h>
#  else
    extern volatile int noshare errno;
    extern volatile int noshare vaxc$errno; /* VMS error code when errno = EVMSERR */
#  define EVMSERR 65535
    extern volatile int noshare sys_nerr;
    extern volatile char noshare * sys_errlist [];
#  endif

#else	/* #ifdef vms */

#ifdef __CYGWIN__

#include <errno.h>

/* What's the purpose of this silly API renaming game ? */
#define sys_nerr _sys_nerr
#define sys_errlist _sys_errlist
/*const char *const *sys_errlist = _sys_errlist;*/
/* from a mail => initializer element is not constant */

#else	/* #ifdef __CYGWIN__ */

#if (defined(BSD) && BSD >= 199306)

#include <errno.h>

#else

# ifdef __linux__
#    include <errno.h>
# endif

  extern int errno;
  extern int sys_nerr;

#  ifndef __USE_BSD
#   ifndef __USE_GNU
/* Why did those Linux kernel hackers have to change the interface here, 
   inserting a completely superfluous "const"?
   All this voguish "assumedly-modern C" crap only increases 
   portability problems and wastes developers' time. 
   It's nothing but a nuisance. */
  extern char * sys_errlist [];
#   endif
#  endif

#endif	/* #else BSD >= 199306 */

#endif	/* #else __CYGWIN__ */

#endif	/* #else vms */

#endif	/* #else __GNUC__ */


/*
 * serrorof delivers the error message of the given errno value.
 * serror delivers the error message of the current errno value.
 * geterrno just returns the current errno value.
 */

char *
serrorof (errnum)
  int errnum;
{
#ifdef __GNUC__
  return strerror (errnum);
#else
  if ((errnum < 0) || (errnum >= sys_nerr)) {
	  static char s [20];
#ifdef vms
	  if (errnum == EVMSERR)
	     build_string (s, "VMS error %d", vaxc$errno);
	  else
#endif
	     build_string (s, "Unknown error %d", errnum);
	  return s;
  } else {
	return (char *) sys_errlist [errnum];
  }
/* Without the (char *) cast, on some systems, the compiler emits stupid 
   warnings about some "const" crap, due to the nonsense 
   that I complained about above */
#endif
}

char *
serror ()
{
  return serrorof (errno);
}

int
geterrno ()
{
  return errno;
}


/*======================================================================*\
|*			Text modification routines			*|
\*======================================================================*/

/*
 * Determine new syntax status, given previous status and current 
 * text pointer.
 */
char
syntax_state (prev, s)
  char prev;
  char * s;
{
  switch (* s) {
	case '<':
		if (strncmp (s, "<%", 2) == 0) {
			return prev | syntax_JSP;
		} else if (prev & syntax_JSP) {
			return prev;
		} else if (strncmp (s, "<!--", 4) == 0) {
			return prev | syntax_comment;
		} else {
			return prev | syntax_HTML;
		}
	case '%':
		if (strncmp (s, "%>", 2) == 0) {
			return prev & ~ syntax_JSP;
		} else {
			return prev;
		}
	case '-':
		if (prev & syntax_JSP) {
			return prev;
		} else if (strncmp (s, "-->", 3) == 0) {
			return prev & ~ syntax_comment;
		} else {
			return prev;
		}
	case '>':
		if (prev & syntax_JSP) {
			return prev;
		} else {
			return prev & ~ syntax_HTML;
		}
	default:
		return prev;
  }

/*
  if (strncmp (s, "<%", 2) == 0) {
	return prev | syntax_JSP;
  } else if (strncmp (s, "%>", 2) == 0) {
	return prev & ~ syntax_JSP;
  } else if (prev & syntax_JSP) {
	return prev;
  } else if (strncmp (s, "<!--", 4) == 0) {
	return prev | syntax_comment;
  } else if (strncmp (s, "-->", 3) == 0) {
	return prev & ~ syntax_comment;
  } else if (* s == '<') {
	return prev | syntax_HTML;
  } else if (* s == '>') {
	return prev & ~ syntax_HTML;
  } else {
	return prev;
  }
*/
}

/*
 * Determine status of HTML on this line:
 *	line->syntax_marker is a bitmask indicating in which kinds of 
 *	syntax constructs the line ends
 * If changing, continue with subsequent lines.
 */
void
update_text_state (line)
  LINE * line;
{
  char * lpoi = line->text;
  char syntax_marker = line->prev->syntax_marker;	/* state at line begin */
  char old_syntax_marker = line->syntax_marker;	/* previous state of line */

  if (dim_HTML == False) {
	return;
  }

  while (* lpoi != '\0') {
	syntax_marker = syntax_state (syntax_marker, lpoi);
	advance_char (& lpoi);
  }
  line->syntax_marker = syntax_marker;
  if (syntax_marker != old_syntax_marker && line->next != tail) {
	update_text_state (line->next);
  }
}

/*
 * make_line installs the buffer into a LINE structure.
 * It returns a pointer to the allocated structure.
 */
static
LINE *
make_line (buffer, length, return_type)
  char * buffer;
  int length;
  lineend_type return_type;
{
  register LINE * new_line = alloc_header ();

  if (new_line == NIL_LINE) {
	ring_bell ();
	error ("Cannot allocate more memory for new line header");
	return NIL_LINE;
  } else {
	new_line->text = alloc (length + 1);
	if (new_line->text == NIL_PTR) {
		ring_bell ();
		error ("Cannot allocate more memory for new line");
		return NIL_LINE;
	} else {
		new_line->shift_count = 0;
		new_line->return_type = return_type;
		strncpy (new_line->text, buffer, length);
		new_line->text [length] = '\0';
		new_line->syntax_marker = syntax_none;	/* undetermined */
		return new_line;
	}
  }
}

/*
 * Line_insert () inserts a new line with text pointed to by 'string'.
 * It returns the address of the new line.
 */
LINE *
line_insert (line, string, len, return_type)
  register LINE * line;
  char * string;
  int len;
  lineend_type return_type;
{
  register LINE * new_line;

/* Allocate space for LINE structure and text */
  new_line = make_line (string, len, return_type);

  if (new_line != NIL_LINE) {
/* Install the line into the double linked list */
	new_line->prev = line;
	new_line->next = line->next;
	line->next = new_line;
	new_line->next->prev = new_line;

/* Adjust information about text attribute state (HTML marker) */
	update_text_state (new_line);

/* Increment total_lines */
	total_lines ++;
  }
  return new_line;
}

/*
 * Insert () insert the string 'string' at the given line and location.
 */
int
insert (line, location, string)
  register LINE * line;
  char * location;
  char * string;
{
  register char * bufp = text_buffer;	/* Buffer for building line */
  register char * textp = line->text;
  char * newtext;
  lineend_type old_return_type;
  lineend_type new_return_type;

  if (viewonly) {
	viewonlyerr ();
	return ERRORS;
  }

  if (length_of (textp) + text_bytes_of (string) >= MAX_CHARS) {
	error ("Line too long");
	return ERRORS;
  }

/* Copy part of line until 'location' has been reached */
  while (textp != location) {
	* bufp ++ = * textp ++;
  }

/* Insert string at this location */
  while (* string != '\0') {
	* bufp ++ = * string ++;
  }
  * bufp = '\0';

/* First, allocate memory for next line contents to make sure the */
/* operation succeeds or fails as a whole */
  newtext = alloc (length_of (text_buffer) + length_of (location) + 1);
  if (newtext == NIL_PTR) {
	ring_bell ();
	error ("Cannot allocate memory for insertion");
	return ERRORS;
  } else { /* Install the new text in this line */
	if (* (string - 1) == '\n') {		/* Insert a new line */
		old_return_type = line->return_type;
		if (old_return_type == lineend_NUL || 
		    old_return_type == lineend_NONE)
		{
		/*	line->return_type = top_line->return_type;	*/
			line->return_type = default_lineend;
			new_return_type = old_return_type;
		} else if (utf8_lineends
			   && ((keyshift & ctrlshift_mask) || (hop_flag > 0))) {
			if (keyshift & ctrl_mask) {
				line->return_type = lineend_LS;
			} else {
				line->return_type = lineend_PS;
			}
			new_return_type = old_return_type;
		} else if (old_return_type == lineend_LS ||
			   old_return_type == lineend_PS)
		{
			if (hop_flag > 0) {
				line->return_type = lineend_PS;
			} else {
				line->return_type = lineend_LS;
			}
			new_return_type = old_return_type;
		} else {
			new_return_type = old_return_type;
		}

		if (line_insert (line, location, length_of (location), 
			new_return_type) == NIL_LINE)
		{
			return ERRORS;
		}
		set_modified ();
	} else {
		/* Append last part of line to text_buffer */
		copy_string (bufp, location);
	}

	free_space (line->text);
	set_modified ();
	line->text = newtext;
	copy_string (line->text, text_buffer);
	update_text_state (line);
	return FINE;
  }
}

/*
 * Line_delete () deletes the argument line out of the line list.
 * The pointer to the next line is returned.
 */
static
LINE *
line_delete (line)
  register LINE * line;
{
  register LINE * next_line = line->next;

  line->prev->return_type = line->return_type;

/* Delete the line */
  line->prev->next = line->next;
  line->next->prev = line->prev;

/* Free allocated space */
  free_space (line->text);
  free_header (line);

/* Decrement total_lines */
  total_lines --;

  return next_line;
}

/*
 * Delete_text () deletes all the characters (including newlines) between 
 * startposition and endposition and fixes the screen accordingly.
 * It displays the number of lines deleted.
 */
int
delete_text (start_line, start_textp, end_line, end_textp)
  LINE * start_line;
  char * start_textp;
  LINE * end_line;
  char * end_textp;
{
  register char * textp = start_line->text;
  register char * bufp = text_buffer;	/* Storage for new line->text */
  LINE * line;
  LINE * after_end = end_line->next;
  int line_cnt = 0;			/* Nr of lines deleted */
  int count = 0;
  int shift = 0;			/* Used in shift calculation */
  int nx = x;
  int ret = FINE;
  char * newtext;
  int newpos_offset = start_textp - textp;
  FLAG isdeleting_lastcombining = False;
  int redraw_cols = 0;
  unsigned long unichar;
  char * cp;

  if (viewonly) {
	viewonlyerr ();
	return ret;
  }

  set_modified ();			/* File has been modified */

  if (combining_mode && encoding_has_combining ()) {
	unichar = unicodevalue (start_textp);
	if (iscombined (unichar, start_textp, start_line->text)) {
		cp = start_textp;
		advance_char (& cp);
		unichar = unicodevalue (cp);
		if (! iscombining_unichar (unichar)) {
			isdeleting_lastcombining = True;
			cp = start_textp;
			do {
				precede_char (& cp, start_line->text);
				unichar = unicodevalue (cp);
			} while (cp != start_line->text && iscombining_unichar (unichar));
			if (iswide (unichar)) {
				redraw_cols = 2;
			} else {
				redraw_cols = 1;
			}
		}
	}
  }

/* Set up new line. Copy first part of start line until start_position. */
  while (textp < start_textp) {
	* bufp ++ = * textp ++;
	count ++;
  }

/* Check if line doesn't exceed MAX_CHARS */
  if (count + length_of (end_textp) >= MAX_CHARS) {
	error ("Line too long");
	return ret;
  }

/* Copy last part of end_line if end_line is not tail */
  copy_string (bufp, (end_textp != NIL_PTR) ? end_textp : "\n");

/* Delete all lines between start and end_position (including end_line) */
  line = start_line->next;
  while (line != after_end && line != tail) {
	/* Here, the original mined compared with end_line->next which has 
	   already been discarded when the comparison should become true.
	   This severe error remained undetected until I ported to MSDOS */
	line = line_delete (line);
	line_cnt ++;
  }

/* Check if last line of file should be deleted */
  if (end_textp == NIL_PTR && length_of (start_line->text) == 1 && total_lines > 1) {
	start_line = start_line->prev;
	(void) line_delete (start_line->next);
	line_cnt ++;
  } else {	/* Install new text */
	newtext = alloc (length_of (text_buffer) + 1);
	if (newtext == NIL_PTR) {
		ring_bell ();
		error ("No more memory after deletion");
		ret = ERRORS;
	} else {
		free_space (start_line->text);
		start_line->text = newtext;
		copy_string (start_line->text, text_buffer);
		update_text_state (start_line);
	}
  }

/* Update screen */
  if (line_cnt == 0) {		/* Check if only one line changed */
	if (shift > 0) {	/* Reprint whole line */
		set_cursor (0, y);
		line_print (y, start_line);
		move_to (nx, y);	/* Reset cur_text */
	} else {			/* Just display last part of line */
		move_address (cur_line->text + newpos_offset, y);
		if (isdeleting_lastcombining) {
			if (redraw_cols == 0 || proportional) {
				set_cursor (0, y);
				line_print (y, start_line);
			} else {
				set_cursor (x - redraw_cols, y);
				put_line (y, start_line, x - redraw_cols, True, False);
			}
		} else {
			put_line (y, start_line, x, True, False);
		}
		set_cursor_xy ();
	}
	return ret;
  }

  shift = last_y;	   /* Save value */
  reset (top_line, y);
  if ((line_cnt <= SCREENMAX - y) && can_delete_line) {
	clear_status ();
	display (y, start_line, 0, y);
	line = proceed (start_line, SCREENMAX - y - line_cnt + 1);
	while (line_cnt -- > 0) {
		delete_line (y + 1);
		scrollbar_scroll_up (y + 1);
		if (line != tail) {
			set_cursor (0, SCREENMAX);
			line_print (SCREENMAX, line);
			line = line->next;
		}
	}
  } else {
	display (y, start_line, shift - y, y);
  }
  move_to (nx, y);
  return ret;
}


/*======================================================================*\
|*				End					*|
\*======================================================================*/
