/*
 *  Project   : tin - a threaded Netnews reader
 *  Module    : misc.c
 *  Author    : I.Lea & R.Skrenta
 *  Created   : 01-04-91
 *  Updated   : 20-06-92
 *  Notes     :
 *  Copyright : (c) Copyright 1991-92 by Iain Lea & Rich Skrenta
 *              You may  freely  copy or  redistribute  this software,
 *              so  long as there is no profit made from its use, sale
 *              trade or  reproduction.  You may not change this copy-
 *              right notice, and it must be included in any copy made
 */

#include	"tin.h"

static char *mailbox_name = (char *) 0;
static int  mailbox_size;


void asfail (file, line, cond)
	char	*file;
	int	line;
	char	*cond;
{
  	fprintf (stderr, "%s: assertion failure: %s (%d): %s\n",
  		progname, file, line, cond);
  	fflush (stderr);
  	
 	/*
 	 * create a core dump
 	 */
#ifdef SIGABRT	
	sigdisp(SIGABRT, SIG_DFL);
 	kill (process_id, SIGABRT);
#else
#	ifdef SIGILL
		sigdisp(SIGILL, SIG_DFL);
 		kill (process_id, SIGILL);
#	else
#		ifdef SIGIOT
			sigdisp(SIGIOT, SIG_DFL);
		 	kill (process_id, SIGIOT);
#		endif
#	endif
#endif
 
	exit(1);
}


void copy_fp (fp_ip, fp_op, prefix)
	FILE *fp_ip;
	FILE *fp_op;
	char *prefix;
{
	extern int errno;
	char buf[8192];

	while (fgets (buf, sizeof (buf), fp_ip) != NULL) {
		if (fprintf (fp_op, "%s%s", prefix, buf) == EOF) {
			if (errno == EPIPE) {
				return;
			}
			sprintf (msg, "Error: fprintf() failed in copy_fp(). errno=%d", errno);
			perror_message (msg, "");
		}
	}
}


char *get_val (env, def)
	char *env;		/* Environment variable we're looking for	*/
	char *def;		/* Default value if no environ value found	*/
{
	char *ptr;

	if ((ptr = (char *) getenv(env)) != NULL)
		return (ptr);
	else
		return (def);
}


int invoke_editor (nam)
	char *nam;
{
	char buf[LEN];
	char *my_editor;
	static char editor[LEN];
	static int first = TRUE;

	if (first) {
		my_editor = (char *) getenv ("VISUAL");

		strcpy (editor, my_editor != NULL ? my_editor : get_val ("EDITOR", DEFAULT_EDITOR));
		first = FALSE;
	}

	if (start_editor_offset) {
		sprintf (buf, "%s +%d %s", editor, start_line_offset, nam);
	} else {
		sprintf (buf, "%s %s", editor, nam);
	}

	wait_message (buf);

	return invoke_cmd (buf);
}


void shell_escape ()
{
	char shell[LEN];
	char *p;

#ifdef SIGTSTP
	sigtype_t (*susp)();
	susp = (sigtype_t *) 0;
#endif

	sprintf (msg, txt_shell_escape, default_shell_command);
	
	if (! prompt_string (msg, shell))
		my_strncpy (shell, get_val ("SHELL", DEFAULT_SHELL), sizeof (shell));

	for (p = shell; *p && (*p == ' ' || *p == '\t'); p++)
		continue;

	if (*p) {
		my_strncpy (default_shell_command, p, sizeof (default_shell_command));
	} else {
		if (default_shell_command[0]) {
			my_strncpy (shell, default_shell_command, sizeof (shell));
		} else {
			my_strncpy (shell, get_val ("SHELL", DEFAULT_SHELL), sizeof (shell));
		}
		p = shell;
	}

	ClearScreen ();
	sprintf (msg, "Shell Command (%s)", p);
	center_line (0, TRUE, msg);
	MoveCursor (INDEX_TOP, 0);
	
	EndWin ();
	Raw (FALSE);

#ifdef SIGTSTP
	if (do_sigtstp)
		susp = signal (SIGTSTP, SIG_DFL);
#endif

	system (p);

#ifdef SIGTSTP
	if (do_sigtstp)
		signal (SIGTSTP, susp);
#endif

	Raw (TRUE);
	InitWin ();

	mail_setup ();

	continue_prompt ();

	if (draw_arrow_mark) {
		ClearScreen ();
	}
}


void tin_done (ret)
	int ret;
{
	extern char index_file[PATH_LEN];
	char group_path[PATH_LEN], *p;
	int ask = TRUE;
	register int i, j;
	
	/*
	 * check if any groups were read & ask if they should marked read
	 */
	if (catchup_read_groups) {
		for (i = 0 ; i < group_top ; i++) {
			if (active[my_group[i]].attribute.read) {
				if (ask) {
					if (prompt_yn (LINES, "Catchup all groups entered during this session? (y/n): ", 'n')) {
						ask = FALSE;
						thread_arts = FALSE;	/* speeds up index loading */
					} else {
						break;
					}
				}
				sprintf (msg, "Catchup %s...", active[my_group[i]].name);
				wait_message (msg);
				my_strncpy (group_path, active[my_group[i]].name, sizeof (group_path));
				for (p = group_path ; *p ; p++) {
					if (*p == '.') {
						*p = '/';
					}
				}
				index_group (active[my_group[i]].name, group_path);
				for (j = 0; j < top; j++) {
					arts[j].unread = ART_READ;
				}
				update_newsrc (active[my_group[i]].name, my_group[i], FALSE);
			}
		}
	}
	nntp_close ();			/* disconnect from NNTP server */
	free_all_arrays ();		/* deallocate all arrays */
	ClearScreen ();
	EndWin ();
	Raw (FALSE);

	if (read_news_via_nntp && xindex_supported) {
		unlink (index_file);
	}

#ifdef INDEX_DAEMON
	unlink (LOCK_FILE);
#endif

	exit (ret);
}

#ifdef USE_MKDIR
mkdir (path, mode)
	char *path;
	int mode;
{
	char buf[LEN];
	struct stat sb;

	sprintf(buf, "mkdir %s", path);
	if (stat (path, &sb) == -1) {
		system (buf);
		chmod (path, mode);
	}
}
#endif

/*
 * hash group name for fast lookup later 
 */

long hash_groupname (group)
	char *group;
{
	unsigned long hash_value;
	unsigned char *ptr = (unsigned char *) group;

	hash_value = *ptr++;

	while (*ptr)
		hash_value = ((hash_value << 1) ^ *ptr++) % TABLE_SIZE;

	return (hash_value);
}


void rename_file (old_filename, new_filename)
	char *old_filename;
	char *new_filename;
{	
	char buf[1024];
	FILE *fp_old, *fp_new;
	
	unlink (new_filename);
	
	if (link (old_filename, new_filename) == -1) {
		if (errno == EXDEV) {	/* create & copy file across filesystem */
			if ((fp_old = fopen (old_filename, "r")) == (FILE *) 0) {
				sprintf (buf, txt_cannot_open, old_filename);
				perror_message (buf, "ONE");
				return;
			}
			if ((fp_new = fopen (new_filename, "w")) == (FILE *) 0) {
				sprintf (buf, txt_cannot_open, new_filename);
				perror_message (buf, "ONE");
				return;
			}
			copy_fp (fp_old, fp_new, "");
			fclose (fp_new);	
			fclose (fp_old);	
			errno = 0;
		} else {
			sprintf (buf, txt_rename_error, old_filename, new_filename);
			perror_message (buf, "THREE");
			return;
		}	
	}
	if (unlink (old_filename) == -1) {
		sprintf (buf, txt_rename_error, old_filename, new_filename);
		perror_message (buf, "TWO");
		return;
	}
}
char *str_dup (str)
	char *str;
{
	char *dup = (char *) 0;

	if (str) {
		dup = my_malloc (strlen (str)+1);
		strcpy (dup, str);
	}
	return dup;
}


int invoke_cmd (nam)
	char *nam;
{
	int ret;
#ifdef SIGTSTP
	sigtype_t (*susp)();
	susp = (sigtype_t *) 0;
#endif

	set_alarm_clock_off ();

	EndWin ();
	Raw (FALSE);

#ifdef SIGTSTP
	if (do_sigtstp)
		susp = signal(SIGTSTP, SIG_DFL);
#endif

#ifdef SIGCHLD
	system (nam);
	ret = system_status;
#else
	ret = system (nam);
#endif

#ifdef SIGTSTP
	if (do_sigtstp)
		signal (SIGTSTP, susp);
#endif

	Raw (TRUE);
	InitWin ();

	set_alarm_clock_on ();
	
	return ret == 0;
}


void draw_percent_mark (cur_num, max_num)
	int cur_num;
	int max_num;
{
	char buf[32];
	int percent = 0;

	if (NOTESLINES <= 0) {
		return;
	}

	if (cur_num <= 0 && max_num <= 0) {
		return;
	}
		
	percent = cur_num * 100 / max_num;
	sprintf (buf, "%s(%d%%) [%d/%d]", txt_more, percent, cur_num, max_num);
	MoveCursor (LINES, (COLS - (int) strlen (buf))-(1+BLANK_PAGE_COLS));
	StartInverse ();	
	fputs (buf, stdout);
	fflush (stdout);
	EndInverse ();
}

void set_real_uid_gid ()
{
	if (local_index)
		return;

	umask (real_umask);
	
#ifdef USE_SETREUID
	if (setreuid (-1, real_uid) == -1) {
		perror_message ("Error setreuid(real) failed", "");
	}
	if (setregid (-1, real_gid) == -1) {
		perror_message ("Error setregid(real) failed", "");
	}
#else	
#  if defined(BSD) && ! defined(sinix)
#    ifdef sun
	if (seteuid (real_uid) == -1) {
		perror_message ("Error setreuid(real) failed", "");
	}
	if (setegid (real_gid) == -1) {
		perror_message ("Error setregid(real) failed", "");
	}
#    else
	if (setreuid (tin_uid, real_uid) == -1) {
		perror_message ("Error setreuid(real) failed", "");
	}
	if (setregid (tin_gid, real_gid) == -1) {
		perror_message ("Error setregid(real) failed", "");
	}
#    endif	/* sun */	
#  else
	if (setuid (real_uid) == -1) {
		perror_message ("Error setuid(real) failed", "");
	}
	if (setgid (real_gid) == -1) {
		perror_message ("Error setgid(real) failed", "");
	}
#  endif
#endif
}


void set_tin_uid_gid ()
{
	if (local_index)
		return;

	umask (0);

#ifdef USE_SETREUID
	if (setreuid (-1, tin_uid) == -1) {
		perror_message ("Error setreuid(tin) failed", "");
	}
	if (setregid (-1, tin_gid) == -1) {
		perror_message ("Error setregid(tin) failed", "");
	}
#else	
#  if defined(BSD) && ! defined(sinix)
#    ifdef sun
	if (seteuid (tin_uid) == -1) {
		perror_message ("Error setreuid(real) failed", "");
	}
	if (setegid (tin_gid) == -1) {
		perror_message ("Error setregid(real) failed", "");
	}
#    else
	if (setreuid (real_uid, tin_uid) == -1) {
		perror_message ("Error setreuid(tin) failed", "");
	}
	if (setregid (real_gid, tin_gid) == -1) {
		perror_message ("Error setregid(tin) failed", "");
	}
#    endif	/* sun */	
#  else
	if (setuid (tin_uid) == -1) {
		perror_message ("Error setuid(tin) failed", "");
	}
	if (setgid (tin_gid) == -1) {
		perror_message ("Error setgid(tin) failed", "");
	}
#  endif
#endif
}


void basename (dirname, program)
	char *dirname;		/* argv[0] */
	char *program;		/* progname is returned */
{
	int i;
	
	strcpy (program, dirname);
	
	for (i=(int) strlen (dirname)-1 ; i ; i--) {
		if (dirname[i] == '/') {
			strcpy (program, dirname+(i+1));
			break;
		}
	}
}


/*
 *  Record size of mailbox so we can detect if new mail has arrived
 */

void mail_setup ()
{
	struct stat buf;

	mailbox_name = get_val ("MAIL", mailbox);

	if (stat (mailbox_name, &buf) >= 0) {
		mailbox_size = buf.st_size;
	} else {
		mailbox_size = 0;
	}
}

/*
 *  Return TRUE if new mail has arrived
 */

int mail_check ()
{
	struct stat buf;

	if (mailbox_name != (char *) 0 &&
		stat (mailbox_name, &buf) >= 0 &&
		mailbox_size < buf.st_size) {
		return TRUE;
	}

	return FALSE;
}

/*
 *  Parse various From: lines into the component mail addresses and
 *  real names
 */

void parse_from (str, addr, name)
	char *str;
	char *addr;
	char *name;
{
	register int c;
	register char *cp, *ncp;
	int gotlt, lastsp, level;

	gotlt = 0;
	lastsp = 0;
	cp = addr;
	ncp = name;
	while (*str == ' ')
		++str;
	while (c = *str++)
		switch (c) {
		case '(':
			ncp = name;
			level = 1;
			while (*str != '\0' && level) {
				switch (c = *str++) {
					case '(':
						*ncp++ = c;
						level++;
						break;
					case ')':
						level--;
						if (level > 0)
							*ncp++ = c;
						break;
					default:
						if (c != '"') {	/* IL */
							*ncp++ = c;
						}	
						break;
				}
			}
			if (*str)
				str++;
			lastsp = 0;
			break;
		case ' ':
			if (str[0] == 'a' && str[1] == 't' && str[2] == ' ')
				str += 3, *cp++ = '@';
			else if (str[0] == '@' && str[1] == ' ')
				str += 2, *cp++ = '@';
			else
				lastsp = 1;
			if (ncp > name)
				*ncp++ = ' ';
			break;
		case '<':
			cp = addr;
			gotlt++;
			lastsp = 0;
			break;
		case '>':
			if (gotlt)
				goto done;
			/* FALL THROUGH CASE */
		default:
			if (lastsp) {
				lastsp = 0;
				*cp++ = ' ';
			}
			*cp++ = c;
			if (! gotlt)
				*ncp++ = c;
			break;
		}
done:
	*cp = 0;
	while (ncp>name && ncp[-1]==' ')
		--ncp;
	*ncp = 0;
	if (*addr == '@') {
		char buf [512];

		strcpy (buf, addr);
		strcpy (addr, "root");
		strcat (addr, buf);
	}
}

/*
 *  Convert a string to a long, only look at first n characters
 */

long my_atol (s, n)
	char *s;
	int n;
{
	long ret = 0;

	while (*s && n--) {
		if (*s >= '0' && *s <= '9')
			ret = ret * 10 + (*s - '0');
		else
			return -1;
		s++;
	}

	return ret;
}

/*
 *  strcmp that ignores case
 */

#define FOLD_TO_UPPER(a)	(islower ((int) (a)) ? toupper ((int) (a)) : (a))

int my_stricmp (p, q)
	char *p;
	char *q;
{
	for (; FOLD_TO_UPPER (*p) == FOLD_TO_UPPER (*q); ++p, ++q) {
		if (*p == '\0') {
			return (0);
		}
	}		

	return (FOLD_TO_UPPER (*p) - FOLD_TO_UPPER (*q));
}

/*
 *  Return a pointer into s eliminating any leading Re:'s.  Example:
 *
 *	  Re: Reorganization of misc.jobs
 *	  ^   ^
 */

char *eat_re (s)
	char *s;
{

	while (*s == 'r' || *s == 'R') {
		if ((*(s+1) == 'e' || *(s+1) == 'E')) {
			if (*(s+2) == ':')
				s += 3;
			else if (*(s+2) == '^' && isdigit(*(s+3)) && *(s+4) == ':')
				s += 5;			/* hurray nn */
			else
				break;
		} else
			break;
		while (*s == ' ')
			s++;
	}

	return s;
}

/*
 *  Hash the subjects (after eating the Re's off) for a quicker
 *  thread search later.  We store the hashes for subjects in the
 *  index file for speed.
 */

long hash_s (s)
	char *s;
{
	long h = 0;
	unsigned char *t = (unsigned char *) s;

	while (*t)
		h = h * 64 + *t++;

	return h;
}

/*
 *  strncpy that stops at a newline and null terminates
 */

void my_strncpy (p, q, n)
	char *p;
	char *q;
	int n;
{
	while (n--) {
		if (! *q || *q == '\n')
			break;
		*p++ = *q++;
	}
	*p = '\0';
}


int untag_all_articles ()
{
	int untagged = FALSE;
	register int i;

	for (i=0 ; i < top ; i++) {
		if (arts[i].tagged) {
			arts[i].tagged = FALSE;
			untagged = TRUE;
		}
	}
	num_of_tagged_arts = 0;

	return (untagged);
}


/*
 * ANSI C strstr () - Uses Boyer-Moore algorithm.
 */
 
char *str_str (text, pattern, patlen)
	char *text;
	char *pattern;
	int patlen;
{
	register unsigned char *p, *t;
	register int i, p1, j, *delta;
	int deltaspace[256];
	int textlen;

	textlen = strlen (text);

	/* algorithm fails if pattern is empty */
	if ((p1 = patlen) == 0)
		return (text);

	/* code below fails (whenever i is unsigned) if pattern too long */
	if (p1 > textlen)
		return (NULL);

	/* set up deltas */
	delta = deltaspace;
	for (i = 0; i <= 255; i++)
		delta[i] = p1;
	for (p = (unsigned char *) pattern, i = p1; --i > 0;)
		delta[*p++] = i;

	/*
	 * From now on, we want patlen - 1.
	 * In the loop below, p points to the end of the pattern,
	 * t points to the end of the text to be tested against the
	 * pattern, and i counts the amount of text remaining, not
	 * including the part to be tested.
	 */
	p1--;
	p = (unsigned char *) pattern + p1;
	t = (unsigned char *) text + p1;
	i = textlen - patlen;
	for (;;) {
		if (*p == *t && memcmp ((p - p1), (t - p1), p1) == 0)
			return ((char *)t - p1);
		j = delta[*t];
		if (i < j)
			break;
		i -= j;
		t += j;
	}
	return (NULL);
}



void get_author (thread, respnum, str)
	int thread;
	int respnum;
	char *str;
{	
	extern int threaded_on_subject;
	int author;
/*
	int len_from = max_from;

	if (thread) {
		if (threaded_on_subject) {
			len_from = max_subj+max_from;
		} else {
			len_from = max_from;
		}
		author = SHOW_FROM_BOTH;
	} else {
		author = show_author;
	} 
*/
	if (thread) {
		if (threaded_on_subject) {
			author = SHOW_FROM_BOTH;
		} else {
			author = show_author;
		}
	} else {
		author = show_author;
	} 
	
	switch (author) { 
		case SHOW_FROM_NONE:
			str[0] = '\0';
			break;
		case SHOW_FROM_ADDR:
			strcpy (str, arts[respnum].from);
			break;
		case SHOW_FROM_NAME:
			strcpy (str, arts[respnum].name);
			break;
		case SHOW_FROM_BOTH:
			if (arts[respnum].name != arts[respnum].from) { 
				sprintf (str, "%s (%s)", arts[respnum].name, arts[respnum].from);
			} else { 
				strcpy (str, arts[respnum].from);
			}
			break;
	}
}


void toggle_inverse_video ()
{
	inverse_okay = !inverse_okay;
	if (inverse_okay) {
#ifndef USE_INVERSE_HACK	
		draw_arrow_mark = FALSE;
#endif		
		info_message (txt_inverse_on);
	} else {
		draw_arrow_mark = TRUE;
		info_message (txt_inverse_off);
	}
}


int get_arrow_key ()
{
	int ch;
	
	ch = ReadCh ();
	if (ch == '[' || ch == 'O')
		ch = ReadCh();
	switch (ch) {
		case 'A':
		case 'D':
		case 'i':
			return KEYMAP_UP;

		case 'B':
		case 'C':
			return KEYMAP_DOWN;

		case 'I':		/* ansi  PgUp */
		case 'V':		/* at386 PgUp */
		case 'S':		/* 97801 PgUp */
		case 'v':		/* emacs style */
			return KEYMAP_PAGE_UP;

		case 'G':		/* ansi  PgDn */
		case 'U':		/* at386 PgDn */
		case 'T':		/* 97801 PgDn */
			return KEYMAP_PAGE_DOWN;

		case 'H':		/* at386  Home */
			return KEYMAP_HOME;
					
		case 'F':		/* ansi   End */
		case 'Y':		/* at386  End */
			return KEYMAP_END;

		case '5':		/* vt200 PgUp */
			ch = ReadCh ();	/* eat the ~  */
			return KEYMAP_PAGE_UP;

		case '6':		/* vt200 PgUp */
			ch = ReadCh ();	/* eat the ~  */
			return KEYMAP_PAGE_DOWN;

		case '1':		/* vt200 PgUp */
			ch = ReadCh ();	/* eat the ~  */
			return KEYMAP_HOME;
					
		case '4':		/* vt200 PgUp */
			ch = ReadCh ();	/* eat the ~  */
			return KEYMAP_END;

		default:
			return KEYMAP_UNKNOWN;
	}
}
