/*
 * Copyright (C) 1992 by Rush Record (rhr@clio.rice.edu)
 * 
 * This file is part of ED.
 * 
 * ED is free software; you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation.
 * 
 * ED is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with ED
 * (see the file COPYING).  If not, write to the Free Software Foundation, 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */
#include "opsys.h"

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

#define MAXRECUR 8

#include "memory.h"
#include "ctyp_dec.h"
#include "rec.h"
#include "window.h"
#include "ed_dec.h"
#include "key_dec.h"

typedef struct def_str *def_ptr;
typedef struct def_str
{
	def_ptr next;	/* next keydef in list */
	Schar value;	/* key value */
	Char lstloaded;	/* flag that list of defined subkeys is correct */
	Int length;	/* length of definition */
	Schar *values;	/* string of subkeys */
	Char *deflst;	/* string of key-is-defined flags */
	Int *repeats;	/* repeat counts for subkeys */
} def_node;

static Int forgiven = 0;
static def_ptr base = 0,last = 0;
static Int level = -1;
static def_ptr def[MAXRECUR];
static Schar *byt[MAXRECUR];
static Char *isdef[MAXRECUR];
static Int *cnt[MAXRECUR];
static Int remain[MAXRECUR];
static Int repeat[MAXRECUR];

static Schar *defbuf;	/* these are used while defining keys */
static Int *rptbuf;
static Int defcount = 0;	/* count of keys in definition */
static Int defining = -1;	/* which window has the "Defining key..." message, or -1 if no definition is in progress */
static Schar defvalue;	/* key being defined */
static Char ignore = 0;	/* flag to prevent evolution when user presses key that is to be defined */
static Char abortkey = 0;	/* for aborting key evolution when bad condition obtains */
static Char ignorealpha = 0;	/* flag to prevent evolution of alpha keys */
static Char has_been_putoff = 0;	/* flag that putoff() was called */

extern Char *window_title();

/******************************************************************************\
|Routine: was_putoff
|Callby: init_term
|Purpose: Checks whether we have to redisplay because we had called putoff();
|Arguments:
|    none
\******************************************************************************/
Int was_putoff()
{
	if(has_been_putoff)
	{
		has_been_putoff = 0;
		return(1);
	}
	return(0);
}

/******************************************************************************\
|Routine: unload_key
|Callby: load_key
|Purpose: Removes a key definition from the key database.
|Arguments:
|    value is the value of the key to toss.
\******************************************************************************/
void unload_key(value)
Schar value;
{
	def_ptr d,l;

	l = (def_ptr)&base;
	for(d = base;d;l = d,d = d->next)
		if(d->value == value)
		{
			l->next = d->next;
			for(last = l;last->next;last = last->next);
			ifree(d->repeats);
			ifree(d->values);
			ifree(d->deflst);
			ifree(d);
			for(d = base;d;d = d->next)	/* clear all list-loaded flags */
				d->lstloaded = 0;
			return;
		}
}

/******************************************************************************\
|Routine: load_key
|Callby: load_key restore_par
|Purpose: Stores a sequence of characters and repeat counts as a key definition
|         in the key database.
|Arguments:
|    value is the value of the key that has been defined.
|    length is the number of character/repeat pairs in the definition.
|    buf is the sequence of characters.
|    rpt is the array of repeat counts.
\******************************************************************************/
void load_key(value,length,buf,rpt)
Int value,length;
Schar *buf;
Int *rpt;
{
	register def_ptr new;

/* remove any existing definition for this key */
	unload_key((Schar)value);
	new = (def_ptr)imalloc(sizeof(def_node));
	new->values = (Schar *)imalloc(length);
	new->deflst = (Char *)imalloc(length);
	new->repeats = (Int *)imalloc(length * sizeof(Int));
	memcpy((Char *)new->values,(Char *)buf,length);
	memcpy(new->repeats,rpt,length * sizeof(Int));
	new->length = length;
	new->value = value;
	if(!base)
		base = new;
	else
		last->next = new;
	new->next = 0;
	last = new;
	for(new = base;new;new = new->next)	/* clear all list-loaded flags */
		new->lstloaded = 0;
}

/******************************************************************************\
|Routine: defined_key
|Callby: init_term load_key
|Purpose: Tests whether a key has a definition associated with it. Returns 1
|         if the key is defined, else 0. Also stores the repeat count
|         associated with the key so it can start evolving.
|Arguments:
|    value is the key we are testing.
|    rept is the repeat count that was applied to the key when it was pressed
|            (or when it was retrieved from another key definition).
\******************************************************************************/
Int defined_key(value,rept)
Schar value;
Int rept;
{
	register Schar *p;
	register Char *q;
	register def_ptr d,e;
	register Int i;

	if(ignore)
		return(0);
	if(defining >= 0 && level < 0 && value != 11)
	{
		defbuf[defcount] = value;
		rptbuf[defcount++] = rept;
	}
	if(level == MAXRECUR-1)
		return(0);
	if(!(value & 0x80))
		if(isalpha(value))
			if(ignorealpha || defining >= 0)
				return(0);
	for(d = base;d;d = d->next)
		if(d->value == value)	/* key is defined */
		{
			def[++level] = d;
			repeat[level] = rept;
			byt[level] = d->values;
			isdef[level] = d->deflst;
			if(!d->lstloaded)
			{
				for(q = d->deflst,p = d->values,i = d->length;i--;p++,q++)	/* load the def list */
				{
					*q = 0;
					for(e = base;e;e = e->next)
						if(e->value == *p)
						{
							*q = 1;	/* this key in definition is itself defined */
							break;
						}
				}
				d->lstloaded = 1;
			}
			remain[level] = d->length;
			cnt[level] = d->repeats;
			if(!has_been_putoff && rept > 19)
			{
				has_been_putoff = 1;
				putoff();
			}
			return(1);
		}
	return(0);
}

/******************************************************************************\
|Routine: next_key
|Callby: init_term load_key
|Purpose: Retrieves the next character/repeat pair in an evolving key. Returns
|         0 if the definition has come to an end (or has been aborted), else 1.
|Arguments:
|    val is the returned key value.
|    rept is the returned repeat count.
\******************************************************************************/
Int next_key(val,rept)
Char *val;
Int *rept;
{
	if(abortkey)
	{
		abortkey = 0;
		level = -1;
		return(0);
	}
	if(!remain[level]--)	/* if we are out of chars at the current level */
		if(--repeat[level] <= 0)	/* if there are no more repetitions at this level */
			return((--level < 0)? 0 : next_key(val,rept));	/* if at top level, fail, else recurse */
		else	/* another repetition at this level */
		{
			byt[level] = def[level]->values;
			isdef[level] = def[level]->deflst;
			remain[level] = def[level]->length - 1;
			cnt[level] = def[level]->repeats;
		}
	*val = *byt[level]++;	/* return next char */
	*rept = *cnt[level]++;
	if(!has_been_putoff && *rept > 19)
	{
		has_been_putoff = 1;
		putoff();
	}
	if(!isalpha(*val) || *val & 0x80)	/* prevent recursion for alpha keys */
		if(isdef[level][byt[level] - def[level]->values - 1])
			if(defined_key(*val,*rept))
				next_key(val,rept);
	return(1);
}

/******************************************************************************\
|Routine: define_key
|Callby: edit inquire
|Purpose: Handles creation of new key definitions. Called when they press ^K.
|Arguments:
|    none
\******************************************************************************/
void define_key()
{
	Int i;
	Char buf[512];
	
/* if definition is in progress, terminate definition */
	if(defining >= 0)
	{
		unload_key(defvalue);
		if(defcount)
			load_key(defvalue,defcount,defbuf,rptbuf);
		ifree(rptbuf);
		ifree(defbuf);
		ignorealpha = 0;
		move(WINDOW[defining].toprow - 1,1);	/* redisplay the file name */
		ers_end();
		reverse();
		i = defining;
		defining = -1;	/* this must be set first */
		putz(window_title(i));
		normal();
		move(CURROW,CURCOL);
		return;
	}
	move(WINDOW[CURWINDOW].toprow - 1,1);
	ers_end();
	reverse();
	putz("Press the key you want to define.");
	normal();
	move(CURROW,CURCOL);
/* start new definition */
	ignore = 1;
	get_next_key(&defvalue);
	ignore = 0;
	if(defvalue == 11)
	{
		move(WINDOW[defining].toprow - 1,1);	/* redisplay the file name */
		ers_end();
		reverse();
		i = defining;
		defining = -1;	/* this must be set first */
		putz(window_title(i));
		normal();
		move(CURROW,CURCOL);
		return;
	}
	move(WINDOW[CURWINDOW].toprow - 1,1);
	ers_end();
	reverse();
	sprintf(buf,"Defining key '%s'...",keyname[defvalue + 128]);
	putz(buf);
	normal();
	move(CURROW,CURCOL);
	defining = CURWINDOW;
	ignorealpha = 1;
	defbuf = (Schar *)imalloc(4096);
	rptbuf = (Int *)imalloc(4096*sizeof(int));
	defcount = 0;
}

/******************************************************************************\
|Routine: store_keys
|Callby: store_param
|Purpose: Stores all key definitions in the user's startup file.
|Arguments:
|    fp is the startup file pointer.
\******************************************************************************/
void store_keys(fp)
FILE *fp;
{
	Int n,pos;
	def_ptr d;
	Char buf[128];

	for(n = 0,d = base;d;d = d->next)
		n++;
	fprintf(fp,"ndefined=%d\n",n);
	for(pos = 0,d = base;d;d = d->next)
	{
		sprintf(buf,"%x ",((int)d->value) & 0xff);
		fputs(buf,fp);
		pos += strlen(buf);
		sprintf(buf,"%d ",d->length);
		fputs(buf,fp);
		pos += strlen(buf);
		for(n = 0;n < d->length;n++)
		{
			sprintf(buf,"%x %x ",((int)d->values[n]) & 0xff,d->repeats[n]);
			fputs(buf,fp);
			if((pos += strlen(buf)) > 80)
			{
				fprintf(fp,"\n");
				pos = 0;
			}
		}
	}
	if(pos)
		fprintf(fp,"\n");
}

/******************************************************************************\
|Routine: definition_inprog
|Callby: inquire ref_window
|Purpose: Tests whether a key is currently being defined. Returns the window
|         in which the "Defining key..." message appears, or -1 if no definition
|         is in progress.
|Arguments:
|    none
\******************************************************************************/
Int definition_inprog()
{
	return(defining);
}

/******************************************************************************\
|Routine: abort_key
|Callby: command copier down_arrow edit killer left_arrow move_eol move_word right_arrow up_arrow wincom
|Purpose: Flags a condition that should make key definitions stop evolving. This
|         would typically be a failed search, an attempt to move off the ends of
|         the window, or an attempt to kill off the end of the window.
|Arguments:
|    none
\******************************************************************************/
void abort_key()
{
	if(!forgiven)
		if(level >= 0)	/* ignore unless key is in progress */
			abortkey = 1;
}

/******************************************************************************\
|Routine: ignore_alpha
|Callby: inquire
|Purpose: Prevents evolution of alpha keys. This is helpful when getting search
|         strings and the like.
|Arguments:
|    none
\******************************************************************************/
void ignore_alpha()	/* prevent alpha evolution */
{
	ignorealpha = 1;
}


/******************************************************************************\
|Routine: see_alpha
|Callby: inquire
|Purpose: Reestablishes evolution of alpha keys. Converse of ignore_alpha.
|Arguments:
|    none
\******************************************************************************/
void see_alpha()
{
	ignorealpha = 0;
}

/******************************************************************************\
|Routine: forgive
|Callby: edit
|Purpose: Can't remember.
|Arguments:
|	none
\******************************************************************************/
void forgive()
{
	forgiven = 1;
}

/******************************************************************************\
|Routine: begrudge
|Callby: edit
|Purpose: Can't remember.
|Arguments:
|	none
\******************************************************************************/
void begrudge()
{
	forgiven = 0;
}

/******************************************************************************\
|Routine: unload_defs
|Callby: command
|Purpose: Unloads all key definitions to a def file.
|Arguments:
|    none
\******************************************************************************/
void unload_defs(file)
Char *file;
{
	FILE *fp;
	Int n,pos,val,ind;
	def_ptr d;
	Char buf[128],buf2[128];

	if((fp = fopen(file,"w")))
	{
		for(d = base;d;d = d->next)
		{
			val = ((int)d->value) + 128;
			sprintf(buf,"key:%s =",keyname[val]);
			fputs(buf,fp);
			pos = strlen(buf);
			for(n = 0;n < d->length;n++)
			{
				ind = ((int)d->values[n]) + 128;
				sprintf(buf,"[%d]",d->repeats[n]);
				if(pos + 1 + strlen(buf) + strlen(keyname[ind]) > 80)
				{
					fprintf(fp,"\n        ");
					pos = 8;
				}
				sprintf(buf2," %s%s",buf,keyname[ind]);
				fputs(buf2,fp);
				pos += strlen(buf2);
			}
			fprintf(fp,"\n");
		}
		fclose(fp);
	}
}

/******************************************************************************\
|Routine: load_defs
|Callby: command
|Purpose: Loads all key definitions from an unloaded def file. Returns the
|         number of matching strings (should be 1) or -1 when the buffer
|         empties.
|Arguments:
|    pp - the pointer in the user buffer.
|    count - a flag indicating that a [n] count should be parsed.
|    pv - the returned key value.
|    pr - the returned repeat count.
\******************************************************************************/
Int get_keyval(pp,count,pv,pr)
Char **pp;
Int count;
Int *pv;
Int *pr;
{
	Char *p,hit[256],c;
	Int i,j,hits;
	
	p = *pp;
	if(*p == '\n' || *p == '\0')
		return(-1);
	while(*p == ' ')	/* skip blanks */
		if(*++p == '\n')
			return(-1);
	if(count)
	{
		if(*p++ != '[')
			return(0);
		for(i = 0;(c = *p++) != ']';i = 10 * i + c - '0');	/* parse the repeat count */
		*pr = i;
	}
	memset(hit,1,sizeof(hit));	/* set hit list */
	hits = 256;
	i = 0;	/* column number in keyname array */
	while(1)
	{
		if((c = *p++) == ' ')
			c = 0;
		else if(c == '\n')
			c = 0;
		for(j = 0;j < 256;j++)
			if(hit[j])
				if(c != keyname[j][i])
				{
					hit[j] = 0;
					hits--;
				}
		if(!c)
			break;
		i++;
	}
	if(hits == 1)
	{
		for(i = 0;i < 256;i++)
			if(hit[i])
				break;
		*pv = i - 128;
	}
	*pp = p;
	return(hits);
}

/******************************************************************************\
|Routine: load_defs
|Callby: command
|Purpose: Loads all key definitions from an unloaded def file.
|Arguments:
|    none
\******************************************************************************/
void load_defs(file)
Char *file;
{
	FILE *fp;
	Schar keys[8192];
	Char buf[2048],errorbuf[2048],*p,*q;	/* keys is signed char */
	Int nkeys,value,val,rpt,i;
	Int repeat[8192];
	def_ptr d,n;
	
	if((fp = fopen(file,"r")))
	{
		for(d = base;d;d = n)	/* remove all defined keys */
		{
			n = d->next;
			ifree(d->repeats);
			ifree(d->values);
			ifree(d->deflst);
			ifree(d);
		}
		base = last = 0;
		while(fgets(buf,sizeof(buf),fp))	/* read from file */
		{
			if(!strncmp(buf,"key:",4))	/* a new key starts */
			{
redo:
				q = p = buf + 4;
				i = get_keyval(&p,0,&value,&rpt);
				if(i != 1)
					goto error;
				while(*p == ' ' || *p == '=')
					p++;
				nkeys = 0;
contin:
				while(1)
				{
					q = p;
					if((i = get_keyval(&p,1,&val,&rpt)) < 0)
						break;	/* get another line */
					if(i != 1)
						goto error;
					repeat[nkeys] = rpt;
					keys[nkeys++] = val;
				}
				if(!fgets(buf,sizeof(buf),fp))	/* end of file, complete def */
				{
					load_key(value,nkeys,keys,repeat);
					break;
				}
				if(!strncmp(buf,"key:",4))	/* new key starts, complete def and redo  */
				{
					load_key(value,nkeys,keys,repeat);
					goto redo;
				}
				p = buf;
				goto contin;
			}
		}
		fclose(fp);
	}
	return;
error:
	fclose(fp);
	if(!i)
		sprintf(errorbuf,"Unrecognized key name:%s",q);
	else if(i > 1)
		sprintf(errorbuf,"Ambiguous key name:%s",q);
	slip_message(errorbuf);
	wait_message();
}

