/*************************************************************************/
/*								   	 */
/*	Classify items interactively using a set of rules		 */
/*	-------------------------------------------------		 */
/*								   	 */
/*************************************************************************/


#include "defns.i"
#include "types.i"


		/*  External data  */

short		MaxAtt, MaxClass, MaxDiscrVal;

ItemNo		MaxItem;

Description	*Item;

DiscrValue	*MaxAttVal;

String		*ClassName,
		*AttName,
		**AttValName,
		FileName = "DF";

char		*SpecialStatus;

short		VERBOSITY = 0,
		TRACE     = 0;


Boolean		FirstTime = true;

PR		*Rule;

RuleNo		NRules = 0,
		*RuleIndex;
 
short		RuleSpace = 0;
 
ClassNo		DefaultClass;


	/*  The interview module uses a more complex description of an
	    case called a "Range Description" with lower and upper bounds.
	    For further information, see consult.c  */

typedef	struct ValRange *RangeDescRec;

struct ValRange
	{
	    Boolean Known, Asked;
	    float LowerBound, UpperBound, *Probability;
	};

RangeDescRec RangeDesc;

float Confidence;

#define	MINCF	0.50		/* minimum cf for useable rule */


/*************************************************************************/
/*								  	 */
/*  Find the best rule for the current case.			  	 */
/*  Note:  leave probability in Confidence				 */
/*								  	 */
/*************************************************************************/


RuleNo BestRule()
/*     -------- 	 */
{
    RuleNo r;
    float cf, RuleStrength();

    Confidence = 0.0;
    
    ForEach(r, 1, NRules)
    {
	cf = RuleStrength(Rule[r]);

	if ( cf > 0.3 )
	{
	    Confidence = cf;
	    return r;
	}
    }

    return 0;
}



/*************************************************************************/
/*								    	 */
/*  Given a rule, determine the strength with which we can conclude	 */
/*  that the current case belongs to the class specified by the RHS	 */
/*  of the rule								 */
/*								    	 */
/*************************************************************************/


float RuleStrength(Rule)
/*    ------------ 	 */
    PR Rule;
{
    short d;
    float RuleProb=1.0, ProbSatisfied();

    ForEach(d, 1, Rule.Size)
    {
	RuleProb *= ProbSatisfied(Rule.Lhs[d]);
	if ( RuleProb < MINCF )
	{
	    return 0.0;
	}
    }

    return ( (1 - Rule.Error) * RuleProb );
}



/*************************************************************************/
/*							   		 */
/*  Determine the probability of the current case description		 */
/*  satisfying the given condition					 */
/*							   		 */
/*************************************************************************/


float ProbSatisfied(c)
/*    ------------- 	 */
    Condition c;
{
    Attribute a;
    char v;
    float AddProb=0.0;
    Test t;
    DiscrValue i;

    t = c->CondTest;
    a = t->Tested;
    v = c->TestValue;

    CheckValue(a, Nil);

    if ( ! RangeDesc[a].Known )
    {
	return 0.0;
    }

    switch ( t->NodeType )
    {
	case BrDiscr:  /* test of discrete attribute */

	    return RangeDesc[a].Probability[v];

	case ThreshContin:  /* test of continuous attribute */

	    if ( RangeDesc[a].UpperBound <= t->Cut )
	    {
		return ( v == 1 ? 1.0 : 0.0 );
	    }
	    else
	    if ( RangeDesc[a].LowerBound > t->Cut )
	    {
		return ( v == 2 ? 1.0 : 0.0 );
	    }
	    else
	    if ( v == 1 )
	    {
		return (t->Cut - RangeDesc[a].LowerBound) /
		       (RangeDesc[a].UpperBound - RangeDesc[a].LowerBound);
	    }
	    else
	    {
		return (RangeDesc[a].UpperBound - t->Cut) /
		       (RangeDesc[a].UpperBound - RangeDesc[a].LowerBound);
	    }

	case BrSubset:  /* subset test on discrete attribute  */

	    ForEach(i, 1, MaxAttVal[a])
	    {
		if ( In(i, t->Subset[v]) )
		{
		    AddProb += RangeDesc[a].Probability[i];
		}
	    }
	    return AddProb;

    } 
    return 0.0;
}



/*************************************************************************/
/*								  	 */
/*		Process a single case				  	 */
/*								  	 */
/*************************************************************************/


    InterpretRuleset()
/*  ---------------- 	 */
{ 
    char Reply;
    Attribute a;
    RuleNo r;

    /*  Initialise  */

    ForEach(a, 0, MaxAtt)
    {
	RangeDesc[a].Asked = false;
    }

    if ( FirstTime )
    {
	FirstTime = false;
	printf("\n");
    }
    else
    {
	printf("\n-------------------------------------------\n\n");
    }

    /*  Find the first rule that fires on the item  */

    if ( r = BestRule() )
    {
	printf("\nDecision:\n");
	printf("\t%s", ClassName[Rule[r].Rhs]);

	if ( Confidence < 1.0 )
	{
	    printf("  CF = %.2f", Confidence);
	}

	printf("\n");
    }
    else
    {
	printf("\nDecision:\n");
	printf("\t%s (default class)\n", ClassName[DefaultClass]);
    }

    /*  Prompt for what to do next  */

    while ( true )
    {
	printf("\nRetry, new case or quit [r,n,q]: ");
	Reply = getchar();
	SkipLine(Reply);
	switch ( Reply )
	{
	  case 'r':  return;
	  case 'n':  Clear(); return;
	  case 'q':  exit(0);
	  default:   printf("Please enter 'r', 'n' or 'q'");
	}
    }
}
    


/*************************************************************************/
/*								  	 */
/*  Main routine for classifying items using a set of rules	  	 */
/*								  	 */
/*************************************************************************/

    main(Argc, Argv)
/*  ---- 	 */
    int Argc;
    char *Argv[];
{
    int o;
    extern char *optarg;
    extern int optind;
    Attribute a;
    RuleNo r;

    PrintHeader("production rule interpreter");

    /*  Process options  */

    while ( (o = getopt(Argc, Argv, "tvf:")) != EOF )
    {
	switch (o)
	{
	    case 't':	TRACE = 1;
			break;
	    case 'v':	VERBOSITY = 1;
			break;
	    case 'f':	FileName = optarg;
			break;
	    case '?':	printf("unrecognised option\n");
			exit(1);
	}
    }

    /*  Initialise  */

    GetNames();
    GetRules();

    if ( TRACE )
    {
	ForEach(r, 1, NRules)
	{
	    PrintRule(r);
	}
	printf("\nDefault class: %s\n", ClassName[DefaultClass]);
    }

    /*  Allocate value ranges  */

    RangeDesc = (struct ValRange *) calloc(MaxAtt+1, sizeof(struct ValRange));

    ForEach(a, 0, MaxAtt)
    {
	if ( MaxAttVal[a] )
	{
	    RangeDesc[a].Probability =
		(float *) calloc(MaxAttVal[a]+1, sizeof(float));
	}
    }

    /*  Consult  */

    Clear();
    while ( true )
    {
	InterpretRuleset();
    }
}