/*
 * Automatic guessing based on probable words.
 *
 * Bob Baldwin, February 1985.
 */

#include	<stdio.h>
#include	<math.h>
#include	"window.h"
#include	"terminal.h"
#include	"layout.h"
#include	"specs.h"
#include	"cipher.h"
#include	"autotri.h"


#define	DEBUG	FALSE

#define	NWORDS		100
#define WDBUFSZ		(8*NWORDS)
#define	WDPERMSZ	40
#define	PWDLABEL1	"Probable word search -- Please Wait"
#define	PWDLABEL2	"Probable word search -- Done"
#define	PWDHELP		"F3 enters guess, ^G undoes it."


extern	char	mcbuf[];
extern	ecinfo	gecinfo;
extern	atrinfo gatrinfo;
extern	atrdraw(), atrfirst(), atrenter(), atrundo();
extern	char	*pwd_init();

/* Gloabal State. */
char	*word_tab[NWORDS];
char	word_buf[WDBUFSZ];

keyer	pwdktab[] = {
		{CACCEPT, atrenter},
		{CUNDO, atrundo},
		{CGO_UP, jogup},
		{CGO_DOWN, jogdown},
		{CGO_LEFT, jogleft},
		{CGO_RIGHT, jogright},
		{0, NULL},
};

/* Routine invoked by user to search of a list of probable words.
 * The window is drawn empty, and then filled in with the guess.
 * Return NULL if command completes ok.
 */
char	*pwdguess(str)
char	*str;			/* Command line */
{
	gwindow	*pwd;
	atrinfo	*pwdi;
	ecinfo	*ecbi;
	int		*dbsperm;
	float	max_score;
	char	*errmsg;
	int		i;
	char	filename[MAXWIDTH+1];

	if ((i = sscanf(str, "%*[^:]: %s %*[^:]: %f",
		filename, &max_score)) != 2)  {
			return("Could not parse both arguments.");
		}

	pwd = &gbstore;
	pwdi = &gatrinfo;
	dbsperm = refperm(dbsgetblk(&dbstore));
	errmsg = pwd_init(filename, mcbuf, dbsperm, pwdi);
	if (errmsg != NULL)  return(errmsg);

	ecbi = pwdi->eci;
	pwdi->min_total_chars = 1;
	pwdi->max_score = max_score;
	pwdi->min_wire_chars = 0;

	gbsswitch(pwd, ((char *) pwdi), pwdktab, atrfirst, wl_noop, atrdraw);

	gblset(&gblabel, PWDLABEL1);
	atrdraw(pwd);
	fflush(stdout);

	pwd_autoguess(pwdi);
	decode(ecbi->ciphertext, ecbi->plaintext, ecbi->perm);

	gblset(&gblabel, PWDLABEL2);
	atrdraw(pwd);

	return(NULL);
}


/* Load a word table from the given file.
 * Format is a word on each line terminated by a blank line
 * Returns error message or NULL.
 */
char *wtab_load_from(filename, charbuf, buffree, wtab, tabsize)
int		buffree, tabsize;
char	*filename, *charbuf;
char	*wtab[];
{
	FILE	*inp;
	char	*wordstart;
	int		wordindex, wordlength;
	int		c;

	if ((inp = fopen(filename, "r"))== NULL)  {
		return("Cannot open file to read probable words.");
		}

	wordindex = 0;
	while(wordindex < tabsize-1)  {
		wordstart = charbuf;
		wordlength = 0;
		while ((c = read_char(inp)) != EOL) {
			*charbuf++ = c;
			wordlength++;
			buffree--;
			if (buffree <= 1)  break;
			}
		*charbuf++ = NULL;
		buffree--;
		if (wordlength == 0  ||  buffree <= 0)  break;
		wtab[wordindex++] = wordstart;
		}

	wtab[wordindex] = NULL;

	fclose(inp);
	return(NULL);
}



/* Fill in probable word info from given ciphertext block.
 * The filter parameters are not set by this routine.
 */
char *pwd_init(filename, cipher, perm, pwdi)
char	*filename;
char	cipher[];
int		perm[];
atrinfo	*pwdi;
{
	int		i;
	char	*errmsg;

	pwdi->eci = &gecinfo;
	errmsg = wtab_load_from(filename, word_buf, WDBUFSZ, word_tab, NWORDS);
	if (errmsg != NULL)  return(errmsg);
	ec_init(cipher, perm, pwdi->eci);
	pwd_guess_init(pwdi);
	return(NULL);
}


/* Per guess initialization.
 */
pwd_guess_init(pwdi)
atrinfo	*pwdi;
{
	pwdi->best_trigram = NULL;
	pwdi->best_score = 10.0;
	pwdi->gcount = 0;
	pwdi->total_score = 0;
	pwdi->best_pvec[0] = NONE;
	pwdi->best_permvec[0].x = NONE;
}



/* Score a word at a given position.
 * Fills in permvec and pvec.
 */
float	pwd_score(pwdi, word, pos, permvec, pvec)
atrinfo		*pwdi;
char		*word;
int			pos;
perment		permvec[];
int			pvec[];
{
	int		added, wordlen;
	char	*p;
	float	score;
	ecinfo	*eci;

	p = word;
	wordlen = 0;
	while (*p++ != NULL)  wordlen++;
	eci = pwdi->eci;
	added = permvec_from_string(pwdi->eci, word, pos, permvec);
	if (added == ERROR)  return(-1.0);
	added = permvec2pvec(pwdi->eci, permvec, pvec, -1, -1);
	if (added == ERROR)  return(-1.0);
	if (added < pwdi->min_total_chars) {
		return(-1.0);
		}

	score = pvec_1score(pvec);
#if DEBUG
	print_pvec(stdout, pvec);
	printf("Putting %s at %d, gets a score of %f\n",
	        word, pos, score);
#endif
	return(score);
}


/* Select the best probable word for a given position.
 * Returns pointer to the word, or NULL.
 * Fills in pwdi with additional information.
 * Filtering parameters are passed in pwdi.
 */
char	*pwd_best(pwdi, pos)
atrinfo	*pwdi;
int		pos;
{
	int		windex;
	float	score;
	perment	permvec[WDPERMSZ];
	int		pvec[BLOCKSIZE+1];

	pwd_guess_init(pwdi);

	for (windex = 0 ; word_tab[windex] != NULL ; windex++)  {
		score = pwd_score(pwdi, word_tab[windex], pos, permvec, pvec);
		if (score < 0.0)  continue;
		pwdi->gcount++;
		pwdi->total_score += score;
		if (score < pwdi->best_score) {
			pwdi->best_score = score;
			pwdi->best_trigram = word_tab[windex];
			pvec_copy(pvec, pwdi->best_pvec);
			permvec_copy(permvec, pwdi->best_permvec, WDPERMSZ);
			}
		}
	if (pwdi->best_score < pwdi->max_score)
		{return(pwdi->best_trigram);}
	else
		{return(NULL);}
}



/* Perform automatic guessing given a set of
 * filter parameters in an atrinfo structure.
 */
pwd_autoguess(pwdi)
atrinfo	*pwdi;
{
	int		pos;
	char	*word;

	for (pos = 0 ; pos < BLOCKSIZE ; pos++) {
		word = pwd_best(pwdi, pos);
		if (word != NULL) {
			accept_permvec(pwdi, pwdi->best_permvec);
			}
		}
}
