/*
 *      XCountdown - X version of the popular Channel 4 quiz show
 *
 * This software comes with NO warranty whatsoever. I therefore take no
 * responsibility for any damages, losses or problems caused through use
 * or misuse of this program.
 *
 * I hereby grant permission for this program to be freely copied and
 * distributed by any means, provided no charge is made for it.
 *
 * Matthew Chapman, csuoq@csv.warwick.ac.uk
 *    June 1995.
 */

/* numround.c - computer solution of numbers round, and input validation and
                 verification of player expressions
*/

#include "xcountdown.h"
#include "globals.h"

/* tmp file - process ID is added to end */
#define TMP "/tmp/.xcountdown.bc.out"

Boolean CheckNum(int num);

typedef struct
{
    int opn1,opn2;
    char opr;
} GoType;

static const int n=6;
int chosen[6];
int targ;
static char ops[] = { '+','-','*','/' };
static GoType exp[6],sol[6];
int len;
int count;

/* recursion computer search - takes two letters, replaces them with the result
   of application of one of the binary operators, checks for target value, and
   calls itself. All solutions are stored, to find shortest, so that, for
   example, 100+1 is chosen instead of 100+(75/25)-1-1. Optimizations are made
   to make sure commutative operations are only performed one way round, and
   division is only performed when the is no remainder.
*/
void Rec(int lev)
{
    int i,j,op;
    int sti,stj;
    Boolean wd;

    if ((count%450)==0)
	ProcessEvent();
    count++;
    for (i=0; i<n; i++)
	if (chosen[i]!=0)
	    for (j=0; j<n; j++)
		if ((i!=j) && (chosen[j]!=0))
		{
		    for (op=0; op<4; op++)
		    {
			/* is operation worth doing? */
			wd=False;
			switch(op)
			{
			case 0: case 1: if (chosen[i]>=chosen[j]) wd=True;
			    break;
			case 2: if ((chosen[i]>=chosen[j]) && (chosen[j]!=1))
			    wd=True;
			break;
			case 3: if ((chosen[i]>=chosen[j]) && (chosen[j]!=1)
				    && ((chosen[i] % chosen[j])==0))
			    wd=True;
			break;
			}					
			if (wd)
			{
			    sti=chosen[i];
			    stj=chosen[j];
			    switch(op)
			    {
			    case 0: chosen[i]=chosen[i]+chosen[j]; break;
			    case 1: chosen[i]=chosen[i]-chosen[j]; break;
			    case 2: chosen[i]=chosen[i]*chosen[j]; break;
			    case 3: chosen[i]=chosen[i]/chosen[j]; break;
			    }
			    chosen[j]=0;
			    /* store operands and operator */
			    exp[lev-1].opn1=sti;
			    exp[lev-1].opn2=stj;
			    exp[lev-1].opr=ops[op];
							
			    /* check for solution */
			    if (chosen[i]==targ)
			    {
				if (lev<len)
				{
				    /* solution is shortest so far - store it */
				    int a;
				    len=lev;
				    for (a=0; a<len; a++)
				    {
					sol[a].opn1=exp[a].opn1;
					sol[a].opn2=exp[a].opn2;
					sol[a].opr=exp[a].opr;
				    }
				}
			    }
			    else
				/* if not at required level, recurse */
				if (lev<5)
				    Rec(lev+1);
			    chosen[i]=sti;
			    chosen[j]=stj;
			}
		    }
		}
}

/* interface to recursive routine */
void Carol(int *list,int dest,char *solstr)
{
    int a,ans=0;
    char buff[128];

    len=7;
    count=0;
    targ=dest;
    for (a=0; a<6; a++)
	chosen[a]=*(list+a);

    Rec(1);
#ifdef DEBUG
    printf("count=%d\n",count);
#endif
    /* process solution into printable form */
    if (len<n+1)
	for (a=0; a<len; a++)
	{
	    switch(sol[a].opr)
	    {
	    case '+': ans=sol[a].opn1+sol[a].opn2; break;
	    case '-': ans=sol[a].opn1-sol[a].opn2; break;
	    case '*': ans=sol[a].opn1*sol[a].opn2; break;
	    case '/': ans=sol[a].opn1/sol[a].opn2; break;
	    }
	    sprintf(buff,"%d%c%d=%d\n",sol[a].opn1,sol[a].opr,sol[a].opn2,ans);
	    strcat(solstr,buff);
	}
#ifdef DEBUG
    printf("solstr=\n%s\n",solstr);
#endif
}


/* checks given string for invalid characters, invalid numbers used, and
   incorrect value upon evaluation. The standard shell command "bc" is used
   for the evaluation.
*/ 
Boolean CheckEntry(String ans,int *list,int targ)
{
    String allowed = "0123456789+-*/() ";
    int a;
    Boolean val=True,ok=False;
    char buff[1024],tname[1024];

    /* check for empty input */
    if (strlen(ans)!=0)
    {
	for (a=0; a<6; a++)
	    chosen[a]=*(list+a);

	/* check for invalid characters */
	if (strspn(ans,allowed)!=strlen(ans))
	    DoMess("An invalid character was used.",3000);
	else
	{
	    int i,num=0;

	    /* check for invalid numbers */		
	    for (i=0; i<(int)strlen(ans); i++)
		if (isdigit(ans[i]))
		    num=(num*10)+ans[i]-'0';
		else
		{
		    if (val)
			val=CheckNum(num);
		    num=0;
		}
	    if (val)
		val=CheckNum(num);
	    if (val)
	    {
		/* evaluate expression */
		FILE *fp;
		float feval;
		int eval;

		sprintf(tname,"%s%d",TMP,(int)getpid());
		sprintf(buff,"echo 'scale=10;%s' | bc >%s",ans,tname);
		system(buff);
		fp = fopen(tname,"r");
		if (fp==NULL)
		{
		    fprintf(stderr,"Error with system call to bc.\n");
		    exit(1);
		}
		fscanf(fp,"%f",&feval);
		/* remove tmp file */
		unlink(tname);
		eval=(int)feval;
		if ((feval-(float)eval)==0.0)
		    if (eval==targ)
			ok=True;
		    else
			DoMess("That evaluates to the wrong value.",3000);
		else
		    DoMess("Floating point values are not allowed.",3000);
	    }
	    else
		DoMess("An invalid number was used.",3000);
	}
    }
    else
	DoMess("No expression was entered.",3000);
    return(ok);
}

/* finds out if given number is in the current list of numbers, removing it if
   it is.
*/
Boolean CheckNum(int num)
{
    if (num>0)
    {
	int i;
#ifdef DEBUG
	printf("num=%d\n",num);
#endif
	for (i=0; (i<6) && (chosen[i]!=num); i++)
	    ;
	if (i==6)
	    return(False);
	else
	    chosen[i]=0;
    }
    return(True);
}
